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

Resuelto martins asked hace 9 años • 27 respuestas

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 .

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 me resulta difícil 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.setStatepara que se actualice items[1].name?

martins avatar Apr 09 '15 18:04 martins
Aceptado

Así es como puedes 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 itemsuna 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
mpen avatar Mar 26 '2018 23:03 mpen

Podrías usar el updateasistente 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 ===, puede editar el estado directamente y forzar que el componente se vuelva a representar; esto es efectivamente lo mismo que la respuesta de @limelights, ya que es sacar un objeto del estado y editarlo.

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

Adición posterior a la edición:

Consulte la lección 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 principal que posee el estado a un componente secundario que necesita desencadenar un cambio de estado.

Jonny Buchanan avatar Apr 09 '2015 11:04 Jonny Buchanan