¿Cómo puedo actualizar state.item[1] en estado usando setState?

16 minutos de lectura

avatar de usuario
Martín

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 un JSFiddle aquí.

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 elaborar? this.setState para que se actualice items[1].name ?

avatar de usuario
mpen

Dado que hay mucha información errónea en este hilo, así es como puede hacerlo sin bibliotecas 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});
},

Puedes combinar los pasos 2 y 3 si quieres:

let item = {
    ...items[1],
    name: 'newName'
}

O puedes hacer todo en una sola línea:

this.setState(({items}) => ({
    items: [
        ...items.slice(0,1),
        {
            ...items[1],
            name: 'newName',
        },
        ...items.slice(2)
    ]
}));

Nota: hice items una matriz. OP usó un objeto. Sin embargo, los conceptos son los mismos.


Puedes ver lo que está pasando 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

  • @TranslucentCloud Oh, sí, los métodos auxiliares definitivamente son buenos, pero creo que todos deberían saber qué sucede debajo del capó 🙂

    – mpen

    5 abr 2018 a las 20:01

  • ¿Por qué necesitamos los pasos 2 y 3? ¿Por qué no podemos mutar el clon directamente después del primer paso?

    – Evmorov

    5 de diciembre de 2018 a las 6:09

  • @Evmorov Porque no es un clon profundo. El paso 1 es simplemente clonar la matriz, no los objetos que contiene. En otras palabras, cada uno de los objetos dentro de la nueva matriz aún “apunta a” los objetos existentes en la memoria: mutar uno mutará el otro (son uno y el mismo). Véase también el items[0] === clone[0] bit en mi ejemplo de terminal en la parte inferior. Triple = comprueba si los objetos se refieren a lo mismo.

    – mpen

    5 de diciembre de 2018 a las 22:12


  • @IanWarburton No realmente. items.findIndex() debería hacer un trabajo corto de eso.

    – mpen

    10 de junio de 2019 a las 21:58


  • Muchas gracias. Estaba luchando por actualizar un elemento de estado en una matriz. Me ahorraste mucho tiempo.

    – Watanabe.N

    19 abr 2021 a las 13:40

avatar de usuario
jonny buchanan

Podrías usar el update ayudante de 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 shouldComponentUpdate() método de ciclo de vida usando ===podría editar el estado directamente y obligar al componente a volver a renderizar; esto es efectivamente lo mismo que la respuesta de @limelights, ya que está sacando un objeto del estado y editándolo.

this.state.items[1].name="updated field name"
this.forceUpdate()

Adición posterior a la edición:

Revisar la Comunicación de componentes simples lección de entrenamiento de reacción para ver un ejemplo de cómo pasar una función de devolución de llamada de un elemento principal que tiene un estado a un componente secundario que necesita activar un cambio de estado.

  • Al leer esta respuesta: stackoverflow.com/a/51018315/2396744, creo que aún necesita el formulario de actualización de la función setstate, que usa la devolución de llamada prevstate.

    – isgoed

    16 oct 2020 a las 12:52

  • Muchas gracias, Jonny, desperdicié un día y finalmente obtuve la solución.

    – Himanshu padia

    5 de marzo de 2021 a las 6:34

avatar de usuario
MarvinVK

¡Camino equivocado!

handleChange = (e) => {
    const { items } = this.state;
    items[1].name = e.target.value;

    // update state
    this.setState({
        items,
    });
};

Como lo señalaron muchos mejores desarrolladores en los comentarios: ¡mutar el estado está mal!

Me tomó un tiempo darme cuenta de esto. Lo anterior funciona pero le quita el poder a React. Por ejemplo componentDidUpdate no verá esto como una actualización porque se modifica directamente.

Asi que la direccion correcta sería:

handleChange = (e) => {
    this.setState(prevState => ({
        items: {
            ...prevState.items,
            [prevState.items[1].name]: e.target.value,
        },
    }));
};

  • ¿ES6 solo porque estás usando “const”?

    – nkkollaw

    21 de julio de 2016 a las 15:34

  • no esta llamando items[1].role = e.target.value estado mutante directamente?

    – antonio

    20 de marzo de 2017 a las 17:33

  • está mutando el estado, esto está totalmente en contra de la idea de mantener el estado inmutable como sugirió la reacción. Esto puede causarle mucho dolor en una aplicación grande.

    – ncúbica

    21 de abril de 2017 a las 1:26

  • @MarvinVK, su respuesta dice “Entonces, la mejor práctica sería:” seguido del uso de “this.forceUpdate();” que no se recomienda ya que setState() podría sobrescribirlo, consulte facebook.github.io/react/docs/react-component.html#state. Es mejor cambiar esto para que no sea confuso para los futuros lectores.

    – James Z.

    1 de agosto de 2017 a las 7:27

  • Solo quería señalar que muchos de los comentarios ahora son irrelevantes debido a ediciones y la direccion correcta es en realidad el camino correcto.

    – jeje

    13 de enero de 2019 a las 15:42

avatar de usuario
neurotransmisor

Para modificar objetos/variables profundamente anidados en el estado de React, normalmente se utilizan tres métodos: Vanilla JavaScript’s Object.assign, ayudante de inmutabilidad 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, existen algunos métodos JavaScript de vainilla adicionales, 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 antipatrón React omnipresente, que inevitablemente conducirá a consecuencias no deseadas. Aprende de la manera correcta.

Comparemos tres métodos ampliamente utilizados.

Dada esta estructura de objeto de estado:

state = {
    outer: {
        inner: 'initial value'
    }
}

Puede usar los siguientes métodos para actualizar la parte más interna inner el valor del campo sin afectar al resto del estado.

1. Object.assign de Vanilla JavaScript

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the shallow copying:', outer.inner) // initial value
    const newOuter = Object.assign({}, outer, { inner: 'updated value' })
    console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

<main id="react"></main>

Manten eso en mente Objeto.asignar no realizará una clonación profundaya que solo copia valores de propiedady es por eso que lo que hace se llama copia superficial (ver comentarios).

Para que esto funcione, solo debemos manipular las propiedades de primitivo tipos (outer.inner), es decir cadenas, números, booleanos.

En este ejemplo, estamos creando una nueva constante (const newOuter...), usando Object.assignque crea un objeto vacío ({}), copias outer objeto ({ inner: 'initial value' }) en él y luego copia un objeto diferente { inner: 'updated value' } sobre eso.

De esta forma, al final el recién creado newOuter constante tendrá un valor de { inner: 'updated value' } desde el inner la propiedad se anuló. Este newOuter es un objeto completamente nuevo, que no está vinculado al objeto en estado, por lo que puede mutarse según sea necesario y el estado permanecerá igual y no cambiará hasta que se ejecute el comando para actualizarlo.

La última parte es usar setOuter() setter para reemplazar el original outer en el estado con una nueva creación newOuter objeto (solo cambiará el valor, el nombre de la propiedad outer no lo hará).

Ahora imagina que tenemos un estado más profundo como state = { outer: { inner: { innerMost: 'initial value' } } }. Podríamos intentar crear el newOuter objeto y rellenarlo con el outer contenidos del estado, pero Object.assign no sera capaz de copiar innerMostel valor de este recién creado newOuter objeto desde innerMost está anidado demasiado profundamente.

Todavía podrías copiar innercomo en el ejemplo anterior, pero dado que ahora es un objeto y no un primitivo, el referencia de newOuter.inner se copiará en el outer.inner en cambio, lo que significa que terminaremos con local newOuter objeto directamente ligado al objeto en el estado.

Eso significa que en este caso las mutaciones de lo creado localmente newOuter.inner afectará directamente a la outer.inner objeto (en estado), ya que de hecho se convirtieron en la misma cosa (en la memoria de la computadora).

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 contienen valores del tipo primitivo.

Si tiene objetos más profundos (segundo nivel o más), que debe actualizar, no los use Object.assign. Te arriesgas a mutar el estado directamente.

2. Clon de LodashDeep

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the deep cloning:', outer.inner) // initial value
    const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
    newOuter.inner="updated value"
    console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

<main id="react"></main>

de lodash clonProfundo es mucho más fácil de usar. realiza un clonación profunda, por lo que es una opción robusta, si tiene un estado bastante complejo con objetos o matrices de varios niveles en su interior. Sólo cloneDeep() la propiedad de estado de nivel superior, mutar la parte clonada de la forma que desee, y setOuter() de vuelta al estado.

3. ayudante de inmutabilidad

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })
  
  React.useEffect(() => {
    const update = immutabilityHelper
    console.log('Before the deep cloning and updating:', outer.inner) // initial value
    const newOuter = update(outer, { inner: { $set: 'updated value' } })
    console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/[email protected]"></script>

<main id="react"></main>

immutability-helper lo lleva a un nivel completamente nuevo, y lo bueno de esto es que no solo puede $set valores para indicar elementos, sino también $push, $splice, $merge (etc.) ellos. Aquí hay un lista de comandos disponible.

Notas al margen

Una vez más, tenga en cuenta que setOuter solo modifica la propiedades de primer nivel del objeto de estado (outer en estos ejemplos), no el profundamente anidado (outer.inner). Si se comportara de otra manera, esta pregunta no existiría.

Cuál es Correcto para tu proyecto?

Si no quiere o no puede usar dependencias externasy tener un estructura estatal simpleAtenerse a Object.assign.

Si usted manipular un estado enorme y/o complejode Lodash cloneDeep es una sabia elección.

Si necesitas capacidades avanzadases decir, si la estructura de su estado es compleja y necesita realizar todo tipo de operaciones en él, intente immutability-helperes una herramienta muy avanzada que se puede utilizar para manipular estados.

… o, ¿usted De Verdad necesita hacer esto en absoluto?

Si tiene datos complejos en el estado de React, tal vez este sea un buen momento para pensar en otras formas de manejarlos. Establecer un objeto de estado complejo en los componentes de React no es una operación sencilla, y sugiero pensar en diferentes enfoques.

Lo más probable es que sea mejor que guardes tus datos complejos en una tienda Redux, configurándolos allí usando reductores y/o sagas y accediendo a ellos usando selectores.

avatar de usuario
jonas

Yo tuve el mismo problema. ¡Aquí hay una solución simple que funciona!

const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });

  • @TranslucentCloud: esto ciertamente no es una mutación directa. la matriz original se clonó, se modificó y luego se volvió a establecer el estado utilizando la matriz clonada.

    – vsync

    16 de julio de 2018 a las 14:51

  • @vsync sí, ahora, después de editar la respuesta original, esto no es una mutación en absoluto.

    – Neurotransmisor

    18 de julio de 2018 a las 13:58

  • @TranslucentCloud – Antes también, mi edición no tiene nada que ver, muchas gracias por la actitud. @Jonas aquí solo cometió un simple error en su respuesta, usando { llaves en lugar de corchetes que había remediado

    – vsync

    18 de julio de 2018 a las 16:19


De acuerdo con la documentación de React en establecerestadousando Object.assign como lo sugieren otras respuestas aquí no es ideal. Debido a la naturaleza de setStateEl comportamiento asincrónico de , 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 sobre 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 preservemos la inmutabilidad del estado. Usando el operador de propagación de la sintaxis ES6 para copiar superficialmente una matriz, crear o actualizar una propiedad de un objeto en un índice dado de la matriz se vería así:

this.setState(prevState => {
    const newItems = [...prevState.items];
    newItems[index].name = newName;
    return {items: newItems};
})

  • @TranslucentCloud: esto ciertamente no es una mutación directa. la matriz original se clonó, se modificó y luego se volvió a establecer el estado utilizando la matriz clonada.

    – vsync

    16 de julio de 2018 a las 14:51

  • @vsync sí, ahora, después de editar la respuesta original, esto no es una mutación en absoluto.

    – Neurotransmisor

    18 de julio de 2018 a las 13:58

  • @TranslucentCloud – Antes también, mi edición no tiene nada que ver, muchas gracias por la actitud. @Jonas aquí solo cometió un simple error en su respuesta, usando { llaves en lugar de corchetes que había remediado

    – vsync

    18 de julio de 2018 a las 16:19


avatar de usuario
henrik andersson

Primero obtenga el elemento que desea, cambie lo que desea en ese objeto y vuelva a establecerlo en el estado. La forma en que está usando el estado al pasar solo un objeto en getInitialState Sería mucho más fácil si usaras un objeto con clave.

handleChange: function (e) {
   item = this.state.items[1];
   item.name="newName";
   items[1] = item;

   this.setState({items: items});
}

  • No, rinde Uncaught TypeError: Cannot read property 'items' of null.

    – Martín

    09/04/2015 a las 11:40

  • No, no lo hará. Lo más probable es que su error se deba a la forma en que lo está haciendo. getInitialState.

    –Henrik Andersson

    09/04/2015 a las 11:40


  • @HenrikAndersson Algo no parece estar bien en tu ejemplo. items no está definido en ninguna parte.

    – Edward D’Souza

    15 de junio de 2016 a las 17:14

  • @EdwardD’Souza ¡Tienes toda la razón! Mi respuesta es mostrar cómo debe definirse y usarse. La forma en que se configura el código del autor de la pregunta no funciona para lo que le gustaría, por lo que se requiere la necesidad de un objeto con clave.

    –Henrik Andersson

    15/06/2016 a las 21:43

  • Este es un antipatrón típico de React, estás asignando item una referencia al propio estado this.state.items[1] variable. Entonces modificas item (item.name = 'newName') y, por lo tanto, mutar el estado directamente, lo cual se desaconseja encarecidamente. En su ejemplo, ni siquiera hay necesidad de llamar this.setState({items: items})porque el estado ya está mutado directamente.

    – Neurotransmisor

    22 de octubre de 2017 a las 10:54


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad