react props practicas paginas organizar mejores imagenes hechas español entender ejemplos con componentes como javascript reactjs ecmascript-6 setstate

javascript - props - Cómo actualizar las propiedades de estado anidado en React



paginas hechas con react js (20)

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

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

Pero actualizar estado como este,

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

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


Renuncia

El estado anidado en React es un diseño incorrecto

Lee this .

Razonamiento detrás de esta respuesta:

SetState de React es solo una conveniencia incorporada, pero pronto te das cuenta de que tiene sus límites. El uso de propiedades personalizadas y el uso inteligente de forceUpdate le brinda mucho más. p.ej:

class MyClass extends React.Component { myState = someObject inputValue = 42 ...

MobX, por ejemplo, abandona el estado por completo y utiliza propiedades observables personalizadas.
Use Observables en lugar de estado en React components.

la respuesta a tu miseria - mira el 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))

Esto es efectivamente igual que

this.state.nested.flag = false this.forceUpdate()

Para la sutil diferencia en este contexto entre forceUpdate y setState vea el ejemplo vinculado.

Por supuesto, esto está abusando de algunos principios básicos, ya que el state debe ser de solo lectura, pero dado que descarta inmediatamente el estado anterior y lo reemplaza por un estado nuevo, está completamente bien.

Advertencia

A pesar de que el componente que contiene el estado se actualizará y volverá a mostrar correctamente ( excepto este problema ) , los accesorios no se propagarán a los niños (consulte el comentario de Spymaster a continuación) . Solo use esta técnica si sabe lo que está haciendo.

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

render( //some complex render with your nested state <ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/> )

Ahora, aunque la referencia para complexNestedProp no cambió ( shouldComponentUpdate )

this.props.complexNestedProp === nextProps.complexNestedProp

el componente se volverá a generar cada vez que se actualice el componente principal, que es el caso después de llamar this.setState o this.forceUpdate en el elemento primario.

Efectos de mutar el estado

Usar estado anidado y mutar el estado directamente es peligroso porque diferentes objetos pueden contener (intencionalmente o no) referencias diferentes (más antiguas) al estado y no necesariamente saber cuándo actualizar (por ejemplo, cuando se usa PureComponent o si shouldComponentUpdate se implementa para devolver false ) O están destinados a mostrar datos antiguos como en el siguiente ejemplo.

Imagine una línea de tiempo que se supone que representa datos históricos, mutar los datos debajo de la mano dará como resultado un comportamiento inesperado, ya que también cambiará los elementos anteriores.

De todos modos, aquí puede ver que Nested PureChildClass no se volvió a procesar debido a que los accesorios no se propagan.


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)


Aquí hay una variación en la primera respuesta dada en este hilo que no requiere ningún paquete adicional, 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 anidado específico, ha configurado todo el objeto. Hice esto creando una variable, newState y extendiendo el contenido del estado actual primero usando el operador de propagación ES2015. Luego, reemplacé el valor de this.state.flag con el nuevo valor (ya que configuré flag: value después de extender el estado actual en el objeto, el campo de flag en el estado actual se anula). Luego, simplemente configuro el estado de someProperty en mi objeto newState .


Aunque la anidación no es realmente cómo debe tratar un estado de componente, a veces es algo fácil para la anidación de un solo nivel.

Por un estado como este

state = { contact: { phone: ''888-888-8888'', email: ''[email protected]'' } address: { street:'''' }, occupation: { } }

Un método reutilizable que he usado se vería así.

handleChange = (obj) => e => { let x = this.state[obj]; x[e.target.name] = e.target.value; this.setState({ [obj]: x }); };

luego simplemente pasa el nombre del obj para cada anidamiento que desea abordar ...

<TextField name="street" onChange={handleChange(''address'')} />


Con ES6:

this.setState({...this.state, property: {nestedProperty: "new value"}})

Lo que es equivalente a:

const newState = Object.assign({}, this.state); newState.property.nestedProperty = "new value"; this.setState(newState);


Crea una copia del estado:
let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))

hacer cambios en este objeto:
someProperty.flag = "false"

ahora actualiza el estado
this.setState({someProperty})


Encontré que esto funciona para mí, tener un formulario de proyecto en mi caso donde, por ejemplo, tiene una identificación y un nombre y prefiero mantener el estado de un proyecto anidado.

return ( <div> <h2>Project Details</h2> <form> <Input label="ID" group type="number" value={this.state.project.id} onChange={(event) => this.setState({ project: {...this.state.project, id: event.target.value}})} /> <Input label="Name" group type="text" value={this.state.project.name} onChange={(event) => this.setState({ project: {...this.state.project, name: event.target.value}})} /> </form> </div> )

¡Házmelo saber!


Hago actualizaciones anidadas con una búsqueda reducida :

Ejemplo:

Las variables anidadas en estado:

state = { coords: { x: 0, y: 0, z: 0 } }

La función:

handleChange = nestedAttr => event => { const { target: { value } } = event; const attrs = nestedAttr.split(''.''); let stateVar = this.state[attrs[0]]; if(attrs.length>1) attrs.reduce((a,b,index,arr)=>{ if(index==arr.length-1) a[b] = value; else if(a[b]!=null) return a[b] else return a; },stateVar); else stateVar = value; this.setState({[attrs[0]]: stateVar}) }

Utilizar:

<input value={this.state.coords.x} onChange={this.handleTextChange(''coords.x'')} />


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

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

Usando lodash/fp set:

import {set} from ''lodash/fp''; const newState = set(["someProperty", "flag"], false, this.state);

Usando lodash/fp merge:

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


Me tomo muy en serio las preocupaciones this torno a la creación de una copia completa del estado de su componente. Dicho esto, sugeriría encarecidamente a https://github.com/mweststrate/immer .

import produce from ''immer''; <Input value={this.state.form.username} onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />

Esto debería funcionar para React.PureComponent (es decir, las comparaciones de estado superficial por React) ya que Immer usa inteligentemente un objeto proxy para copiar eficientemente un árbol de estado arbitrariamente profundo. Immer también es más seguro para los tipos de letra en comparación con bibliotecas como Immutability Helper, y es ideal para usuarios de Javascript y Typecript por igual.

Función de utilidad mecanografiada

function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s: Draft<Readonly<S>>) => any) { comp.setState(produce(comp.state, s => { fn(s); })) } onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}


Otras dos opciones aún no mencionadas:

  1. Si tiene un estado profundamente anidado, considere si puede reestructurar los objetos secundarios para sentarse 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 que figuran en los documentos de Redux . Recomiendo Immer ya que le permite escribir código de manera mutante 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 escribir en una línea

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


Para establecer setState para un objeto anidado, puede seguir el siguiente enfoque, ya que creo que setState no maneja 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 propagación crea solo una copia anidada de nivel del objeto. Si su estado está altamente anidado como:

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

Puede configurar State utilizando 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 sintaxis anterior se vuelve fea a medida que el estado se anida cada vez más y, por lo tanto, le recomiendo que use el paquete immutability-helper para actualizar el estado.

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


Para hacer las 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

<input value={this.state.name} onChange={this.updateState} type="text" name="name" /> <input value={this.state.grandParent.parent1.child} onChange={this.updateState} type="text" name="grandParent.parent1.child" /> <input value={this.state.grandParent.parent2.child} onChange={this.updateState} type="text" name="grandParent.parent2.child" />

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


Sé que es una vieja pregunta, pero aún quería compartir cómo lo logré. Asumiendo que el estado en el constructor se ve así:

constructor(props) { super(props); this.state = { loading: false, user: { email: "" }, organization: { name: "" } }; this.handleChange = this.handleChange.bind(this); }

Mi función handleChange es así:

handleChange(e) { const names = e.target.name.split("."); const value = e.target.type === "checkbox" ? e.target.checked : e.target.value; this.setState((state) => { state[names[0]][names[1]] = value; return {[names[0]]: state[names[0]]}; }); }

Y asegúrese de nombrar las entradas en consecuencia:

<input type="text" name="user.email" onChange={this.handleChange} value={this.state.user.firstName} placeholder="Email Address" /> <input type="text" name="organization.name" onChange={this.handleChange} value={this.state.organization.name} placeholder="Organization Name" />


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

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 objetivo a la función de asignación para asegurarse de que el estado no esté mutado directamente como señaló Carkod.


Usé esta solución.

Si tiene un estado anidado 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 la reasigna con valores cambiados

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 de eventos

<input type="text" onChange={this.handleChange} " name="friendName" />


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 manejas tu estado como un "objeto normal". La magia ocurre detrás de escena con objetos proxy.


A veces las respuestas directas no son las mejores :)

Version corta:

este codigo

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

debería simplificarse como algo así

this.state = { somePropertyFlag: true }

Versión larga:

Actualmente no debería querer trabajar con estado anidado en React . Porque React no está orientado a trabajar con estados anidados y todas las soluciones propuestas aquí parecen piratas informáticos. No usan el marco pero luchan con él. Sugieren escribir código no tan claro con el propósito dudoso de agrupar algunas propiedades. Por lo tanto, 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é sucederá si cambia solo un valor de child1 ? React no volverá a representar la vista porque usa una comparación superficial y descubrirá que parent propiedad parent no cambió. Por cierto, mutar el objeto de estado directamente se considera una mala práctica en general.

Por lo tanto, debe volver a crear todo el objeto parent . Pero en este caso nos encontraremos con otro problema. Reaccionar 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 una lógica complicada en shouldComponentUpdate() pero preferiría detenerme aquí y usar una solución simple de la versión corta.


stateUpdate = () => { let obj = this.state; if(this.props.v12_data.values.email) { obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email } this.setState(obj) }