Cómo actualizar las propiedades de estado nested en React

Estoy tratando de organizar mi estado usando propiedades anidadas como esta:

this.state = { someProperty: { flag:true } } 

Pero actualizando el estado así,

 this.setState({ someProperty.flag: false }); 

no funciona ¿Cómo se puede hacer esto correctamente?

    Para establecer setState para un objeto nested, puede seguir el enfoque a continuación, ya que creo que setState no maneja las actualizaciones anidadas.

     var someProperty = {...this.state.someProperty} someProperty.flag = true; this.setState({someProperty}) 

    La idea es crear un objeto ficticio realizar operaciones en él y luego reemplazar el estado del componente con el objeto actualizado

    Ahora, el operador de difusión crea solo una copia anidada de nivel del objeto. Si su estado está muy nested como:

     this.state = { someProperty: { someOtherProperty: { anotherProperty: { flag: true } .. } ... } ... } 

    Podría establecer State usando el operador de propagación en cada nivel como

     this.setState(prevState => ({ ...prevState, someProperty: { ...prevState.someProperty, someOtherProperty: { ...prevState.someProperty.someOtherProperty, anotherProperty: { ...prevState.someProperty.someOtherProperty.anotherProperty, flag: false } } } })) 

    Sin embargo, la syntax anterior es cada vez más fea a medida que el estado se anida cada vez más, por lo que te recomiendo que uses el paquete immutability-helper para actualizar el estado.

    Vea esta respuesta sobre cómo actualizar el estado con immutability helper .

    Para escribirlo en una linea

     this.setState({ someProperty: { ...this.state.someProperty, flag: false} }); 

    Si está utilizando ES2015, tiene acceso a Object.assign. Puede usarlo de la siguiente manera para actualizar un objeto nested.

     this.setState({ someProperty: Object.assign({}, this.state.someProperty, {flag: false}) }); 

    Combina las propiedades actualizadas con las existentes y utiliza el objeto devuelto para actualizar el estado.

    Editar: se agregó un objeto vacío como destino a la función de asignación para asegurarse de que el estado no se mute directamente, como señaló carkod.

    A veces las respuestas directas no son las mejores 🙂

    Version corta:

    este código

     this.state = { someProperty: { flag: true } } 

    debe simplificarse como algo así como

     this.state = { somePropertyFlag: true } 

    Versión larga:

    Actualmente no deberías querer trabajar con el estado nested en React . Porque React no está orientado a trabajar con estados nesteds y todas las soluciones propuestas aquí se ven como hacks. No usan el marco sino que luchan con él. Sugieren escribir un código no tan claro para el dudoso propósito de agrupar algunas propiedades. Entonces son muy interesantes como respuesta al desafío pero prácticamente inútiles.

    Imaginemos el siguiente estado:

     { parent: { child1: 'value 1', child2: 'value 2', ... child100: 'value 100' } } 

    ¿Qué pasará si cambia solo el valor de child1 ? React no volverá a representar la vista porque usa una comparación superficial y encontrará que parent propiedad parent no cambió. Por cierto, mutar el objeto de estado directamente se considera una mala práctica en general.

    Entonces necesitas volver a crear el objeto parent completo. Pero en este caso encontraremos otro problema. React pensará que todos los niños han cambiado sus valores y los volverá a representar a todos. Por supuesto, no es bueno para el rendimiento.

    Todavía es posible resolver ese problema escribiendo alguna lógica complicada en shouldComponentUpdate() pero preferiría detenerme aquí y usar una solución simple de la versión corta.

    Hay muchas bibliotecas para ayudar con esto. Por ejemplo, usando inmutability-helper :

     import update from 'immutability-helper'; const newState = update(this.state, { someProperty: {flag: {$set: false}}, }; this.setState(newState); 

    O usando lodash / fp :

     import {merge} from 'lodash/fp'; const newState = merge(this.state, { someProperty: {flag: false}, }); 

    Aquí hay una variación de la primera respuesta dada en este hilo que no requiere paquetes adicionales, bibliotecas o funciones especiales.

     state = { someProperty: { flag: 'string' } } handleChange = (value) => { const newState = {...this.state.someProperty, flag: value} this.setState({ someProperty: newState }) } 

    Para establecer el estado de un campo nested específico, ha establecido el objeto completo. Hice esto creando una variable, newState y difundiendo el contenido del estado actual primero usando el operador de extensión ES2015. Luego, reemplacé el valor de this.state.flag con el nuevo valor (ya que establecí flag: value después de propagar el estado actual en el objeto, el campo de flag en el estado actual se anula). Entonces, simplemente establezco el estado de someProperty a mi objeto newState .

    lo mejor de todo – ver ejemplo aquí

    Hay otra forma más corta de actualizar cualquier propiedad anidada.

     this.setState(state => { state.nested.flag = false state.another.deep.prop = true return state }) 

    En una linea

     this.setState(state => (state.nested.flag = false, state)) 

    Por supuesto, esto está abusando de algunos principios básicos, ya que el state debe ser de solo lectura, pero ya que está descartando inmediatamente el estado anterior y reemplazándolo con un nuevo estado, está completamente bien.

    Advertencia

    Aunque el componente que contiene el estado se actualizará y reenviará correctamente ( excepto este gotcha ) , los accesorios no podrán propagarse a los niños (ver el comentario del maestro espía a continuación) . Solo use esta técnica si sabe lo que está haciendo.

    Por ejemplo, puede pasar un stackr plano modificado que se actualiza y pasa fácilmente.

     render( //some complex render with your nested state  ) 

    Ahora bien, aunque la referencia para complexNestedProp no haya cambiado ( shouldComponentUpdate )

     this.props.complexNestedProp === nextProps.complexNestedProp 

    El componente se reenviará cada vez que se actualicen las actualizaciones de los componentes principales, que es el caso después de llamar a this.setState o this.forceUpdate en el elemento primario.

    Renuncia

    El estado nested en React es un diseño incorrecto

    Lee esta excelente respuesta .

    También puede ir por este camino (que me parece más legible):

     const newState = Object.assign({}, this.state); newState.property.nestedProperty = 'newValue'; this.setState(newState); 

    Usé esta solución.

    Si tiene un estado nested como este:

      this.state = { formInputs:{ friendName:{ value:'', isValid:false, errorMsg:'' }, friendEmail:{ value:'', isValid:false, errorMsg:'' } } 

    puede declarar la función handleChange que copia el estado actual y lo reasigna con los valores modificados

     handleChange(el) { let inputName = el.target.name; let inputValue = el.target.value; let statusCopy = Object.assign({}, this.state); statusCopy.formInputs[inputName].value = inputValue; this.setState(statusCopy); } 

    aquí el html con el oyente del evento

      

    Otras dos opciones aún no mencionadas:

    1. Si tiene un estado profundamente nested, considere si puede reestructurar los objetos secundarios para que se sienten en la raíz. Esto hace que los datos sean más fáciles de actualizar.
    2. Hay muchas bibliotecas útiles disponibles para manejar el estado inmutable enumerado en los documentos de Redux . Recomiendo Immer ya que te permite escribir código de forma mutativa pero maneja la clonación necesaria detrás de escena. También congela el objeto resultante para que no pueda mutarlo accidentalmente más tarde.

    Para hacer cosas genéricas, trabajé en las respuestas de @ ShubhamKhatri y @ Qwerty.

    objeto de estado

     this.state = { name: '', grandParent: { parent1: { child: '' }, parent2: { child: '' } } }; 

    controles de entrada

        

    Método updateState

    setState como la respuesta de @ ShubhamKhatri

     updateState(event) { const path = event.target.name.split('.'); const depth = path.length; const oldstate = this.state; const newstate = { ...oldstate }; let newStateLevel = newstate; let oldStateLevel = oldstate; for (let i = 0; i < depth; i += 1) { if (i === depth - 1) { newStateLevel[path[i]] = event.target.value; } else { newStateLevel[path[i]] = { ...oldStateLevel[path[i]] }; oldStateLevel = oldStateLevel[path[i]]; newStateLevel = newStateLevel[path[i]]; } } this.setState(newstate); } 

    setState como la respuesta de @ Qwerty

     updateState(event) { const path = event.target.name.split('.'); const depth = path.length; const state = { ...this.state }; let ref = state; for (let i = 0; i < depth; i += 1) { if (i === depth - 1) { ref[path[i]] = event.target.value; } else { ref = ref[path[i]]; } } this.setState(state); } 

    Nota: Estos métodos anteriores no funcionarán para matrices

    Usamos Immer https://github.com/mweststrate/immer para manejar este tipo de problemas.

    Acabo de reemplazar este código en uno de nuestros componentes

     this.setState(prevState => ({ ...prevState, preferences: { ...prevState.preferences, [key]: newValue } })); 

    Con este

     import produce from 'immer'; this.setState(produce(draft => { draft.preferences[key] = newValue; })); 

    Con immer maneja su estado como un “objeto normal”. La magia sucede detrás de la escena con objetos proxy.

    Encontré que esto funcionaba para mí, tenía un formulario de proyecto en mi caso en el que, por ejemplo, tiene una identificación y un nombre, y prefiero mantener el estado para un proyecto nested.

     return ( 

    Project Details

    this.setState({ project: {...this.state.project, id: event.target.value}})} /> this.setState({ project: {...this.state.project, name: event.target.value}})} />
    )

    ¡Házmelo saber!

    Algo como esto podría ser suficiente,

     const isObject = (thing) => { if(thing && typeof thing === 'object' && typeof thing !== null && !(Array.isArray(thing)) ){ return true; } return false; } /* Call with an array containing the path to the property you want to access And the current component/redux state. For example if we want to update `hello` within the following obj const obj = { somePrimitive:false, someNestedObj:{ hello:1 } } we would do : //clone the object const cloned = clone(['someNestedObj','hello'],obj) //Set the new value cloned.someNestedObj.hello = 5; */ const clone = (arr, state) => { let clonedObj = {...state} const originalObj = clonedObj; arr.forEach(property => { if(!(property in clonedObj)){ throw new Error('State missing property') } if(isObject(clonedObj[property])){ clonedObj[property] = {...originalObj[property]}; clonedObj = clonedObj[property]; } }) return originalObj; } const nestedObj = { someProperty:true, someNestedObj:{ someOtherProperty:true } } const clonedObj = clone(['someProperty'], nestedObj); console.log(clonedObj === nestedObj) //returns false console.log(clonedObj.someProperty === nestedObj.someProperty) //returns true console.log(clonedObj.someNestedObj === nestedObj.someNestedObj) //returns true console.log() const clonedObj2 = clone(['someProperty','someNestedObj','someOtherProperty'], nestedObj); console.log(clonedObj2 === nestedObj) // returns false console.log(clonedObj2.someNestedObj === nestedObj.someNestedObj) //returns false //returns true (doesn't attempt to clone because its primitive type) console.log(clonedObj2.someNestedObj.someOtherProperty === nestedObj.someNestedObj.someOtherProperty)