Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: how to update state.item[1] in state using setState?

I'm creating an app where the user can design his own form. E.g. specify name of the field and details of which other columns that should be included.

The component is available as a JSFiddle here.

My initial state looks like this:

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>     );   } 

I want to update the state when the user changes any of the values, but I'm having a hard time to target the correct object:

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>     );   } }); 

How should I craft this.setState to get it to update items[1].name ?

like image 292
martins Avatar asked Apr 09 '15 11:04

martins


People also ask

How do you update part of state in React?

To update our state, we use this. setState() and pass in an object. This object will get merged with the current state. When the state has been updated, our component re-renders automatically.

How do you update an object in state?

State can hold any kind of JavaScript value, including objects. But you shouldn't change objects that you hold in the React state directly. Instead, when you want to update an object, you need to create a new one (or make a copy of an existing one), and then set the state to use that copy.

Can you update state directly React?

React will then look at the virtual DOM, it also has a copy of the old virtual DOM, that is why we shouldn't update the state directly, so we can have two different object references in memory, we have the old virtual DOM as well as the new virtual DOM.


1 Answers

Since there's a lot of misinformation in this thread, here's how you can do it without helper libs:

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}); }, 

You can combine steps 2 and 3 if you want:

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

Or you can do the whole thing in one line:

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

Note: I made items an array. OP used an object. However, the concepts are the same.


You can see what's going on in your terminal/console:

❯ 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 
like image 72
mpen Avatar answered Oct 21 '22 17:10

mpen