javascript - subproperty - Reaccionar: ¿cómo actualizar state.item[1] en estado usando setState?
setstate array react (19)
¿Qué tal crear otro componente (para el objeto que necesita ir a la matriz) y pasar lo siguiente como accesorios?
- índice de componente: el índice se utilizará para crear / actualizar en la matriz.
- Función set: esta función coloca datos en la matriz en función del índice del componente.
this.state.items[1] = ''new value'';
var cloneObj = Object.assign({}, this.state.items);
this.setState({items: cloneObj });
Aquí se puede pasar {index} en función de la posición donde se utiliza este SubObjectForm.
y setSubObjectData puede ser algo como esto.
var udpateditem = this.state.items.find(function(item) {
return item.name == "field_1" });
udpateditem.name= "New updated name"
this.setState(prevState => ({
items:prevState.dl_name_template.filter(function(item) {
return item.name !== "field_1"}).concat(udpateditem)
}));
En SubObjectForm, this.props.setData se puede llamar en el cambio de datos como se indica a continuación.
import _ from ''lodash'';
this.state.var_name = _.assign(this.state.var_name, {
obj_prop: ''changed_value'',
});
Estoy creando una aplicación donde el usuario puede diseñar su propio formulario. Por ejemplo, especifique el nombre del campo y los detalles de qué otras columnas deben incluirse.
El componente está disponible como JSFiddle here .
Mi estado inicial se ve así:
var DynamicForm = React.createClass({
getInitialState: function() {
var items = {};
items[1] = { name: ''field 1'', populate_at: ''web_start'',
same_as: ''customer_name'',
autocomplete_from: ''customer_name'', title: '''' };
items[2] = { name: ''field 2'', populate_at: ''web_end'',
same_as: ''user_name'',
autocomplete_from: ''user_name'', title: '''' };
return { items };
},
render: function() {
var _this = this;
return (
<div>
{ Object.keys(this.state.items).map(function (key) {
var item = _this.state.items[key];
return (
<div>
<PopulateAtCheckboxes this={this}
checked={item.populate_at} id={key}
populate_at={data.populate_at} />
</div>
);
}, this)}
<button onClick={this.newFieldEntry}>Create a new field</button>
<button onClick={this.saveAndContinue}>Save and Continue</button>
</div>
);
}
Quiero actualizar el estado cuando el usuario cambia cualquiera de los valores, pero tengo dificultades para apuntar al objeto correcto:
var PopulateAtCheckboxes = React.createClass({
handleChange: function (e) {
item = this.state.items[1];
item.name = ''newName'';
items[1] = item;
this.setState({items: items});
},
render: function() {
var populateAtCheckbox = this.props.populate_at.map(function(value) {
return (
<label for={value}>
<input type="radio" name={''populate_at''+this.props.id} value={value}
onChange={this.handleChange} checked={this.props.checked == value}
ref="populate-at"/>
{value}
</label>
);
}, this);
return (
<div className="populate-at-checkboxes">
{populateAtCheckbox}
</div>
);
}
});
¿Cómo debo crear
this.setState
para que actualice los
items[1].name
?
Como hay mucha información errónea en este hilo, así es como puede hacerlo sin libs auxiliares:
handleChange: function (e) {
// 1. Make a shallow copy of the items
let items = [...this.state.items];
// 2. Make a shallow copy of the item you want to mutate
let item = {...items[1]};
// 3. Replace the property you''re intested in
item.name = ''newName'';
// 4. Put it back into our array. N.B. we *are* mutating the array here, but that''s why we made a copy first
items[1] = item;
// 5. Set the state to our new copy
this.setState({items});
},
Puede combinar los pasos 2 y 3 si lo desea:
let item = {
...items[1],
name: ''newName''
}
O puede hacer todo en una línea:
this.setState(({items}) => ({
items: [
...items.slice(0,1),
{
...items[1],
name: ''newName'',
},
...items.slice(2)
]
}));
Nota: hice los
items
una matriz.
OP usó un objeto.
Sin embargo, los conceptos son los mismos.
Puedes ver lo que sucede en tu terminal / consola:
❯ node
> items = [{name:''foo''},{name:''bar''},{name:''baz''}]
[ { name: ''foo'' }, { name: ''bar'' }, { name: ''baz'' } ]
> clone = [...items]
[ { name: ''foo'' }, { name: ''bar'' }, { name: ''baz'' } ]
> item1 = {...clone[1]}
{ name: ''bar'' }
> item1.name = ''bacon''
''bacon''
> clone[1] = item1
{ name: ''bacon'' }
> clone
[ { name: ''foo'' }, { name: ''bacon'' }, { name: ''baz'' } ]
> items
[ { name: ''foo'' }, { name: ''bar'' }, { name: ''baz'' } ] // good! we didn''t mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don''t need to clone items 0 and 2 because we''re not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied
Como ninguna de las opciones anteriores era ideal para mí, terminé usando el mapa:
state = {items: [{name: ''red-one'', value: 100}, {name: ''green-one'', value: 999}]}
De acuerdo con la documentación de React en
setState
, usar
Object.assign
como lo sugieren otras respuestas aquí no es ideal.
Debido a la naturaleza del comportamiento asincrónico de
setState
, las llamadas posteriores que utilizan esta técnica pueden anular las llamadas anteriores y causar resultados no deseados.
En cambio, los documentos de React recomiendan usar la forma de actualización de
setState
que opera en el estado anterior.
Tenga en cuenta que al actualizar una matriz u objeto
debe devolver una nueva matriz u objeto,
ya que React requiere que mantengamos la inmutabilidad del estado.
El uso del operador de propagación de la sintaxis ES6 para copiar superficialmente una matriz, creando o actualizando una propiedad de un objeto en un índice dado de la matriz se vería así:
item = Object.assign({}, this.state.items[1], {name: ''newName''});
items[1] = item;
this.setState({items: items});
Desplazaría el cambio del identificador de funciones y agregaría un parámetro de índice
setSubObjectData: function(index, data){
var arrayFromParentObject= <retrieve from props or state>;
var objectInArray= arrayFromParentObject.array[index];
arrayFromParentObject.array[index] = Object.assign(objectInArray, data);
}
al componente de formulario dinámico y páselo al componente PopulateAtCheckboxes como accesorio. A medida que recorre sus elementos, puede incluir un contador adicional (llamado índice en el código a continuación) para pasar al cambio de identificador como se muestra a continuación
<input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>
Finalmente, puede llamar a su escucha de cambios como se muestra a continuación aquí
handleChange: function (index) {
var items = this.state.items;
items[index].name = ''newName'';
this.setState({items: items});
},
El siguiente fragmento de código fue fácil para mi cerebro aburrido. Retirar el objeto y reemplazarlo por el actualizado
let ItemsCopy = []
let x = this.state.Items.map((entry) =>{
if(entry.id == ''theIDYoureLookingFor'')
{
entry.PropertyToChange = ''NewProperty''
}
ItemsCopy.push(entry)
})
this.setState({Items:ItemsCopy});
Es muy simple
Primero, extraiga todo el objeto de elementos del estado, actualice la parte del objeto de elementos como desee y vuelva a poner todo el objeto de elementos en estado a través de setState.
handleChange: function (e) {
items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects.
items[1].name = ''newName''; // update the items object as needed
this.setState({ items }); // Put back in state
}
Mutación libre:
// given a state
state = {items: [{name: ''Fred'', value: 1}, {name: ''Wilma'', value: 2}]}
// This will work without mutation as it clones the modified item in the map:
this.state.items
.map(item => item.name === ''Fred'' ? {...item, ...{value: 3}} : item)
this.setState(newItems)
No mutes el estado en su lugar.
Puede causar resultados inesperados.
¡He aprendido mi lección!
Siempre trabaje con una copia / clon,
Object.assign()
es bueno:
this.setState(prevState => {
const newItems = [...prevState.items];
newItems[index].name = newName;
return {items: newItems};
})
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Para modificar objetos / variables profundamente anidados en el estado de React, generalmente se utilizan tres métodos: vanilla JS
Object.assign
,
immutability-helper
y
cloneDeep
de
Lodash
.
También hay muchas otras librerías de terceros menos populares para lograr esto, pero en esta respuesta, cubriré solo estas tres opciones.
Además, hay algunos métodos que no usan nada más que JavaScript vainilla, como la distribución de matrices (consulte la respuesta de @ mpen por ejemplo), pero no son muy intuitivos, fáciles de usar y capaces de manejar todas las situaciones de manipulación de estado.
Como se señaló innumerables veces en los comentarios más votados a las respuestas, cuyos autores proponen una mutación directa del estado, simplemente no hagas eso . Este es un anti-patrón de Reacción ubicuo, que inevitablemente conducirá a consecuencias no deseadas. Aprende de la manera correcta.
Comparemos tres métodos ampliamente utilizados.
Nota : estos ejemplos utilizan la forma más antigua para actualizar el estado, con componentes de clase y ciclos de vida. Newer Hooks API tiene una sintaxis diferente, pero la idea sigue siendo la misma. Eso significa que todos estos ejemplos siguen siendo válidos.
Dada esta estructura de estado:
state = {
foo: {
bar: ''initial value''
}
}
1. Vanilla JavaScript''s Object.assign
(...essential imports)
class App extends Component {
state = {
foo: {
bar: ''initial value''
}
}
componentDidMount() {
console.log(this.state.foo.bar) // initial value
const foo = Object.assign({}, this.state.foo, { bar: ''further value'' })
console.log(this.state.foo.bar) // initial value
this.setState({ foo }, () => {
console.log(this.state.foo.bar) // further value
})
}
(...rest of code)
Tenga en cuenta que Object.assign no realizará una clonación profunda , ya que solo copia los valores de propiedad , y es por eso que lo que se llama una copia superficial (ver comentarios).
Para que esto funcione, solo debemos manipular los elementos de
nivel superior de
este objeto (
state.foo
).
Y sus valores (
state.foo.bar
) deben ser
primitive
(cadenas, números, booleanos).
En este ejemplo, estamos creando una nueva constante (
const foo...
), usando
Object.assign
, que crea un objeto vacío (
{}
), copia el objeto
state.foo
(
{ bar: ''initial value'' }
) en y luego copia un objeto diferente
{ bar: ''further value'' }
sobre él.
Entonces, al final, la constante
foo
recién creada tendrá un valor de
{ bar: ''further value'' }
ya que la propiedad de la
bar
se anuló.
Este
foo
es un objeto completamente nuevo, que no está vinculado al objeto de estado, por lo que puede mutar según sea necesario y el estado no cambiará.
La última parte es usar
setState()
setter para reemplazar el
state.foo
original en el estado con un objeto
foo
recién creado.
Ahora imagine que tenemos un estado más profundo como
state = { foo: { bar: { baz: ''initial value'' } } }
.
Podríamos intentar crear un nuevo objeto
foo
y
Object.assign
con los contenidos
foo
del estado, pero
Object.assign
no podrá copiar el valor de
baz
en este objeto
foo
recién creado, ya que
baz
está anidado demasiado profundamente.
Todavía podría copiar la
bar
, como en el ejemplo anterior, pero dado que ahora es un objeto y no una primitiva, la referencia de
state.foo.bar
se copiará en su lugar, lo que significa que terminaremos con un objeto
foo
local directamente vinculado a el estado.
Eso significa que en este caso cualquier mutación del
foo
creado localmente afectará al objeto
state.foo
, ya que de hecho apuntan a lo mismo.
Object.assign
por lo tanto, solo funcionará si tiene una estructura de estado profundo de un nivel relativamente simple con miembros más internos que contengan valores del tipo primitivo.
Si tiene objetos más profundos (segundo nivel o más), que debe actualizar, no use
Object.assign
.
Corre el riesgo de mutar directamente.
2. El clon de Lodash Profundo
(...essential imports)
import cloneDeep from ''lodash.clonedeep''
class App extends Component {
state = {
foo: {
bar: ''initial value''
}
}
componentDidMount() {
console.log(this.state.foo.bar) // initial value
const foo = cloneDeep(this.state.foo)
foo.bar = ''further value''
console.log(this.state.foo.bar) // initial value
this.setState({ foo }, () => {
console.log(this.state.foo.bar) // further value
})
}
(...rest of code)
El clonDeep de
cloneDeep
es mucho más simple de usar.
Realiza
una clonación profunda
, por lo que es una opción sólida si tiene un estado bastante complejo con objetos o matrices de varios niveles dentro.
Simplemente
cloneDeep()
la propiedad de estado de nivel superior, mute la parte clonada lo que quiera y
setState()
en el estado.
3. ayudante de inmutabilidad
(...essential imports)
import update from ''immutability-helper''
class App extends Component {
state = {
foo: {
bar: ''initial value''
}
};
componentDidMount() {
console.log(this.state.foo.bar) // initial value
const foo = update(this.state.foo, { bar: { $set: ''further value'' } })
console.log(this.state.foo.bar) // initial value
this.setState({ foo }, () => {
console.log(this.state.foo.bar) // further value
});
}
(...rest of code)
immutability-helper
lleva a un nivel completamente nuevo, y lo bueno de esto es que no solo puede
$set
valores para establecer elementos, sino también
$push
,
$splice
,
$merge
(etc.).
Aquí hay una
lista de comandos
disponibles.
Notas al margen
Nuevamente, tenga en cuenta que
this.setState()
solo modifica las
propiedades
de
primer nivel
del objeto de estado (propiedad
foo
en este caso), no las profundamente anidadas (
foo.bar
).
Si se comportara de una manera diferente, esta pregunta no existiría.
Y, por cierto,
this.setState({ foo })
es solo una abreviatura de
this.setState({ foo: foo })
.
Y
() => { console.log(this.state.foo.bar) }
justo después de
{ foo }
es una devolución de llamada que se ejecuta inmediatamente después de que
setState
haya establecido el estado.
Conveniente, si necesita hacer algunas cosas después de que hizo su trabajo (en nuestro caso, para mostrar el estado inmediatamente después de que se configuró).
¿Cuál es el adecuado para su proyecto?
Si no quiere o puede usar
dependencias externas
, y tiene una
estructura de estado simple
,
Object.assign
con
Object.assign
.
Si
manipulas un estado enorme y / o complejo
, el clonDeep de
cloneDeep
es una buena elección.
Si necesita
capacidades avanzadas
, es decir, si su estructura de estado es compleja y necesita realizar todo tipo de operaciones en ella, pruebe
immutability-helper
, es una herramienta muy avanzada que puede usarse para la manipulación de estados.
... o necesitas hacer esto?
Si tiene datos complejos en el estado de React, quizás este sea un buen momento para pensar en otras formas de manejar estos datos. Establecer un estado complejo en React no es una operación sencilla, y sugeriré encarecidamente que piense en diferentes enfoques.
Lo más probable es que sea mejor que guarde sus datos complejos en la tienda Redux, configurándolos allí con reductores y / o sagas y accediendo a ellos utilizando selectores.
Primero obtenga el elemento que desea, cambie lo que desea en ese objeto y vuelva a configurarlo en el estado.
La forma en que usa el estado pasando solo un objeto en
getInitialState
sería mucho más fácil si usara un objeto con clave.
const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });
Prueba con el código:
this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:''new_name''}) })
Pruebe esto, definitivamente funcionará, en otro caso lo intenté pero no funcionó
import _ from ''lodash''; this.state.var_name = _.assign(this.state.var_name, { obj_prop: ''changed_value'', });
Puede usar el
asistente de
update
inmutabilidad para esto
:
this.setState({
items: update(this.state.items, {1: {name: {$set: ''updated field name''}}})
})
O si no le importa poder detectar cambios en este elemento en un método del ciclo de vida
shouldComponentUpdate()
usando
===
, puede editar el estado directamente y obligar al componente a volver a renderizar; esto es efectivamente lo mismo que @ la respuesta de los protagonistas, ya que saca un objeto del estado y lo edita.
this.state.items[1].name = ''updated field name''
this.forceUpdate()
Adición posterior a la edición:
Consulte la lección de Comunicación de componentes simples de react-training para ver un ejemplo de cómo pasar una función de devolución de llamada de un componente primario con retención de estado a un componente secundario que necesita activar un cambio de estado.
Si necesita cambiar solo una parte de la
Array
, tiene un componente de reacción con el estado establecido en.
{ Object.keys(this.state.items).map(function (key, index) {
var item = _this.state.items[key];
var boundHandleChange = _this.handleChange.bind(_this, index);
return (
<div>
<PopulateAtCheckboxes this={this}
checked={item.populate_at} id={key}
handleChange={boundHandleChange}
populate_at={data.populate_at} />
</div>
);
}, this)}
Es mejor actualizar el
red-one
en la
Array
siguiente manera:
<input type="radio" name={''populate_at''+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>
Use el evento en
handleChange
para descubrir el elemento que ha cambiado y luego actualícelo.
Para eso, es posible que deba cambiar alguna propiedad para identificarla y actualizarla.
Ver violín https://jsfiddle.net/69z2wepo/6164/
Yo tuve el mismo problema. ¡Aquí hay una solución simple que funciona!
handleChange: function (e) {
item = this.state.items[1];
item.name = ''newName'';
items[1] = item;
this.setState({items: items});
}
o si tiene una lista generada dinámicamente y no conoce el índice pero solo tiene la clave o la identificación:
const itemIndex = this.state.items.findIndex(i=> i.name === ''red-one'');
const newItems = [
this.state.items.slice(0, itemIndex),
{name: ''red-one'', value: 666},
this.state.items.slice(itemIndex)
]
this.setState(newItems)
¡Camino equivocado!
handleChange = (e) => {
const { items } = this.state;
items[1].name = e.target.value;
// update state
this.setState({
items,
});
};
Como lo señalaron muchos desarrolladores mejores en los comentarios: ¡ mutar el estado está mal!
Me llevó un tiempo resolver esto.
Lo anterior funciona pero quita el poder de React.
Por ejemplo,
componentDidUpdate
no verá esto como una actualización porque se modifica directamente.
Entonces la forma correcta sería:
handleChange = (e) => {
this.setState(prevState => ({
items: {
...prevState.items,
[prevState.items[1].name]: e.target.value,
},
}));
};