简体   繁体   中英

state updates on event handler

State gets updated without having to set it explicitly

I ran to this by accident and it confused me. As far as I know you do it as following:

1-create a copy of the current state 2-modify the copy 3-assign the modified copy to state hence updating it.

This is how I do it and it works fine.

handleChange(index, event) {
    const {name, value} = event.target;
    this.setState((prevState) => {
        const newState = [...prevState.items];
        newState[index][name]= value;
        return{items: newState};
    });

But the strange and confusing thing is that the below code also works as well but without having to explicitly assign it in the last line as the above.it is updating as soon as I update the copy which is Not a reference of it.

handleChange(index, event) {
    const {name, value} = event.target;
    this.setState((prevState) => {
        const newState = [...prevState.items];
        newState[index][name]= value;

        return{}; //it works as long as I have the brackets.
    });

From the code in your question, it looks like your state has a structure similar to the following:

state = { items: [{name1: 'index0Value1', name2: 'index0Value2'}, {name1: 'index1Value1', name2: 'index1Value2'}] };

where items is an array of objects.

When you do:

const newState = [...prevState.items];

you are copying the array only, but the objects within newState 's array are still shared with the original state. So you could push a new object into your array without affecting the original, but when you do:

newState[index][name]= value;

you are changing that object in both the original state and your new state. Run the snippet below to see this demonstrated.

 const prevState = { items: [{name1: 'index0Value1', name2: 'index0Value2'}, {name1: 'index1Value1', name2: 'index1Value2'}] }; document.getElementById('origbefore').innerHTML = 'prevState before='+JSON.stringify(prevState); const newItems = [...prevState.items]; newItems[0]['name1']='tryingToOnlyChangeNewState'; const newState = { items: newItems }; document.getElementById('origafter').innerHTML = 'prevState after='+JSON.stringify(prevState); document.getElementById('new').innerHTML = 'newState='+JSON.stringify(newState); 
 <div id="origbefore"></div> <div id="origafter"></div> <div id="new"></div> 

So in both examples, your assignment is changing prevState, and since setState supports passing in a partial state which gets merged in with the previous state, when you return an empty object, React merges that with the mutated previous state and then re-renders with the result.

You are using spread syntax to copy your state. Spread syntax only does shallow copies. So when you change a nested property of your new object it also mutates the original one. This is why your second code works. Your mutated state merged with the current state, which is itself, and you see the mutated result. Do not mutate your state.

Most of the time you should use method likes map , filter to mutate the newly copied object and just change the vale without mutating the original state. So, you can mix those methods with spread syntax or Object.assign but do not directly mutate the copied one.

Since we don't know the exact shape of your data I'm just mimicking something like the code below. You can use this technique if you have an index.

 class App extends React.Component { state = { items: [{ foo: "a foo" }, { bar: "a bar" }, { baz: "a baz" }], }; handleChange(index, event) { const { name, value } = event.target; const { items } = this.state; const newItems = Object.assign([], items, { [index]: { ...items[index], [name]: value }, }); this.setState({ items: newItems }); } render() { return ( <div> {this.state.items.map((item, index) => ( <input name={Object.keys(item)} key={Object.keys(item)} index={index} onChange={(event) => this.handleChange(index, event)} /> ))} <div> {this.state.items.map((item) => ( <p key={Object.values(item)}>{Object.values(item)}</p> ))} </div> </div> ); } } ReactDOM.render( <App />, document.getElementById("root") ); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <div id="root"></div> 

But, if you change this code as you do in your second example, you will see that it won't work since we are not mutating the original state here.

 class App extends React.Component { state = { items: [{ foo: "a foo" }, { bar: "a bar" }, { baz: "a baz" }], }; handleChange(index, event) { const { name, value } = event.target; const { items } = this.state; const newItems = Object.assign([], items, { [index]: { ...items[index], [name]: value }, }); // this.setState({ items: newItems }); this.setState({}); } render() { return ( <div> {this.state.items.map((item, index) => ( <input name={Object.keys(item)} key={Object.keys(item)} index={index} onChange={(event) => this.handleChange(index, event)} /> ))} <div> {this.state.items.map((item) => ( <p key={Object.values(item)}>{Object.values(item)}</p> ))} </div> </div> ); } } ReactDOM.render( <App />, document.getElementById("root") ); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <div id="root"></div> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM