React: How do I update state.item[1] on setState? (with JSFiddle)

React: How do I update state.item[1] on setState? (with JSFiddle)

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 (

{ Object.keys(this.state.items).map(function (key) {
var item = _this.state.items[key];
return (

);
}, this)}

);
}

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 (

);
}, this);
return (

{populateAtCheckbox}

);
}
});

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

Solutions/Answers:

Solution 1:

You could use the update immutability helper for this:

this.setState({
  items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})

Or if you don’t care about being able to detect changes to this item in a shouldComponentUpdate() lifecycle method using ===, you could edit the state directly and force the component to re-render – this is effectively the same as @limelights’ answer, as it’s pulling an object out of state and editing it.

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

Post-edit addition:

Check out the Simple Component Communication lesson from react-training for an example of how to pass a callback function from a state-holding parent to a child component which needs to trigger a state change.

Solution 2:

Wrong way!

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

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

As pointed out by a lot of better developers in the comments: mutating the state is wrong!

Took me a while to figure this out. Above works but it takes away the power of React. For example componentDidUpdate will not see this as an update because it’s modified directly.

So the right way would be:

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

Solution 3:

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.

Related:  Javascript window.print() in chrome, closing new window or tab instead of cancelling print leaves javascript blocked in parent window

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

Solution 4:

To modify deeply nested objects/variables in the React’s state, typically three methods are used: vanilla JS Object.assign, immutability-helper and cloneDeep from Lodash. There are also plenty of other less popular third-party libs to achieve this, but in this answer, I’ll cover just these three options. Also, there are some methods which use nothing else than vanilla JavaScript, like array spreading, (see @mpen’s answer for example), but they are not very intuitive, easy to use and capable to handle all state manipulation situations.

As was pointed innumerable times in top voted comments to answers, whose authors propose a direct mutation of state, just don’t do that. This is a ubiquitous React anti-pattern, which will inevitably lead you to unwanted consequences. Learn the right way.

Let’s compare three widely used methods.

Given this state structure:

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)

Keep in mind, that Object.assign will not perform a deep cloning, since it only copies property values, and that’s why what it does is called a shallow copying (see comments).

Related:  JavaScript seconds to time string with format hh:mm:ss

For this to work, we should only manipulate this object’s top-level items (state.foo). And their values (state.foo.bar) should be primitive (strings, numbers, booleans).

In this example, we’re creating a new constant (const foo...), using Object.assign, which creates an empty object ({}), copies state.foo object ({ bar: 'initial value' }) into it and then copies a different object { bar: 'further value' } over it. So, in the end, the newly created foo constant will hold a value of { bar: 'further value' } since the bar property got overridden. This foo is a brand new object, which is not linked to the state object, so it can be mutated as needed and the state will not change.

The last part is to use setState() setter to replace the original state.foo in the state with a newly created foo object.

Now imagine we have a more deep state like state = { foo: { bar: { baz: 'initial value' } } }. We could try to create a new foo object and populate it with the foo contents from the state, but Object.assign will not be able to copy baz value to this newly created foo object since baz is nested too deep. You could still copy bar, like in the example above, but since it’s an object now and not a primitive, the reference from state.foo.bar will be copied instead, which means that we will end up with local foo object directly tied to the state. That means that in this case any mutations of the locally created foo will affect the state.foo object, since they are in fact pointing to the same thing.

Object.assign therefore will only work if you have a relatively simple one level deep state structure with innermost members holding values of the primitive type.

If you have deeper objects (2nd level or more), which you should update, don’t use Object.assign. You risk mutating state directly.

2. Lodash’s cloneDeep

(...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)

Lodash’s cloneDeep is way more simple to use. It performs deep cloning, so it is a robust option if you have a fairly complex state with multi-level objects or arrays inside. Just cloneDeep() the top-level state property, mutate the cloned part whatever you please, and setState() it back to the state.

Related:  How can you sort an array without mutating the original array?

3. immutability-helper

(...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 takes it to the whole new level, and the cool thing about it is that it can not only $set values to state items, but also $push, $splice, $merge (etc.) them. Here is a list of commands available.

Side notes

Again, keep in mind, that this.setState() only modifies the first-level properties of the state object (foo property in this case), not the deeply nested (foo.bar). If it behaved another way, this question wouldn’t exist.

And by the way, this.setState({ foo }) is just a shorthand for this.setState({ foo: foo }). And () => { console.log(this.state.foo.bar) } after the { foo } is a callback which gets executed immediately after setState have set the state. Convenient, if you need to do some things after it did its job (in our case to display the state immediately after it was set).

Which one is right for your project?

If you don’t want or can use external dependencies, and have a simple state structure, stick to Object.assign.

If you manipulate a huge and/or complex state, Lodash’s cloneDeep is a wise choice.

If you need advanced capabilities, i.e. if your state structure is complex and you need to perform all kinds of operations on it, try immutability-helper, it’s a very advanced tool which can be used for state manipulation.

Solution 5:

First get the item you want, change what you want on that object and set it back on the state.
The way you’re using state by only passing an object in getInitialState would be way easier if you’d use a keyed object.

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

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

Solution 6:

I had the same problem. Here’s a simple solution that works !

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