In the official React docs for State and Lifecycle methods, section "State Updates May be Asynchronous:
https://reactjs.org/docs/state-and-lifecycle.html
They say when updating state based on previous state, we should pass a function to setState() to get the previous state as the first argument:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
It's hard to imagine how the "Wrong" way can possibly lead to problems. What is an example of code where the "Wrong" way can lead to errors?
For a case like this with just one call setState there is nothing wrong. However, since React batches state update, it's not guaranteed to work. I can give you a simple example
this.setState({ count: this.state.count + 10 });
this.setState({ count: this.state.count + 1 });
//The end result is count+1 instead of count+11
//This will work correctly
this.setState((state) => ({count: state.count+10 }));
this.setState((state) => ({count: state.count+1 }));
Demo
Related
According to React docs, I found there are two forms of setState:
Form 1: setState({someState: someValue})
Form 2: setState((preState) => ({someState: doSomething(preState)}))
Form 1 sets state directly, form 2 sets state by using a callback function.
In the Using state correctly section, I've been told that form 1 may not be safe. Does this mean I should always to use form 2 to set state correctly? I soon noticed there is another example here which uses form 1 to update state. Is it an incorrect example?
The form 1 and form 2 may both right, but in which situations is it perfectly safe to use form 1, and in which situations should I use form 2?
I wrote a note on setState. I hope it will help you to use it properly. If you read it sincerely and understand it, you're gonna be better at using it to manage state.
setState
It's asynchronous
state = { count: 0};
increment() {
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
console.log(this.state.count) // 0
}
increment()
console.log(this.state.count); // 1
And, the final value of this.state.count will be 1 after completion of the calling incemenent()
Because React batch all calls up, and figure out the result and then efficiently make that change. Kind of this pure JavaScript code, merging where the last one wins
newState = Object.assign(
{},
firstSetStateCall,
secondSetStateCall,
thirdSetStateCall,
);
So, we can say here everything has to do with JavaScript object merging. So there's another cool way, where we pass a function in setState instead of object.
state = { count: 0};
increment() {
this.setState( (state) => { return { count: state.count + 1} } );
this.setState( (state) => { return { count: state.count + 1} } );
this.setState( (state) => { return { count: state.count + 1} } );
}
increment();
console.log(this.state.count) // 3
This time we will get 3 because earlier it was possible to merge objects but it's not possible to merge functions so it works like synchronous.
But another nice application of this method of passing parameters in this.setState is you can implement logic before returning the objects from the function
this.setState( (state) => { if(state.count === 0) return { count: state.count + 1} } );
Not only that, the function we pass inside setState takes another parameter, props.
this.setState((state, props) => { //play here })
But, the function we're passing it could grow messy by time, so what? Just make a regular JavaScript function
and pass it to the setState
this.setState(fn)
If SetState is asynchronous how we can do an operation just after the state gets updated?
setState actually takes two arguments, second one of these two is callback function, that is invoked after state is updated,
this.setState (
(state, props) => {
// code here
},
() => {console.log("updated state", this.state)}
)
The choice of form depends on whether the next state uses the value of the current state.
If the new state relies on the current state, use form 2. Otherwise use form 1.
What is the difference between the two syntax?
setValues(values => ({
...values, [event.target.name]: event.target.value
}))
setValues({
[event.target.name]: event.target.value
})
Based upon the name setValues I assume you are referring to functional component state. (useState hook updates don't work quite the same as class-based component's setState lifecycle function)
Using the spread syntax allows for maintaining existing state, i.e. the new update [event.target.name]: event.target.value is merged into current state.
Given state { 'foo': 'bar' }
setValues(values => ({
...values, ['bizz']: 'buzz'
}))
New state { 'foo': 'bar', 'bizz': 'buzz' }
Without spreading in the previous state you are simply overwriting it with just an object {[event.target.name]: event.target.value}, so all previous state is lost.
Given state { 'foo': 'bar' }
setValues({
['bizz']: 'buzz'
})
New state { 'bizz': 'buzz' }
There are actually a couple things going on here. First is the spread syntax, the other is what is called a functional update. Functional updates allow the update to access the current state and make changes. This is a necessity when the next state depends on the previous state, like incrementing counters, and multiple state updates can be queued up during each render cycle.
setCount(count => count +1)
In the case of a form component where each property is an independent piece of state, then the following syntax is ok since each update to a field overwrites the current value:
setValues({
...values,
[fieldName]: fieldValue
})
React may batch multiple setState() calls into a single update for performance.
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
For example, this code may fail to update the counter:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
We used an arrow function above, but it also works with regular functions:
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
Read more here on their official documentation
the 'values' parameter contains the current values held in state , so by using the spread operator you preserve the values held in the current state and add in new ones.
In the second example you are just setting the state to the event.target.name and overwriting the previously held state.
For example , suppose the app had called setValues three times with three events : event1 , event2 and event3
In the first case you wrote about your state would be { event1, event2, event3 }. in the second case your state would be { event3 }
Why do we have a callback in setState?
I am new to react and I am trying to understand but I am not able to understand fully.
increasePrice = () => {
this.setState({
price: this.state.price + 1
})
this.props.getPriceData(this.state.price)
}
setstate is async process;its not updated
example if you want to pass the data after modified you need to use callback as below
increasePrice = () => {
this.setState({
price: this.state.price + 1
},()=>{
this.props.getPriceData(this.state.price)
})}
As mentioned in the doc:
https://reactjs.org/docs/react-component.html#setstate
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
You should never modify the state object directly, but instead use the updater function provided by setState():
// Bad
this.state.counter = this.state.counter + this.props.step;
// Good
this.setState((state, props) => {
return {
counter: state.counter + props.step
};
});
setState is Asynchronous in nature. If you want to use the updated state just after setting it Callback of state is used.
Example-
setState(
{ name: "ABC" },
() => console.log(this.state)
);
// => { name: "ABC" }
Read this SO post to know when to use it.
According to the docs, React batches setState if it is provided with a callback
this.setState(prevState => {value: prevState + 1 })
Now, assume we have an instance method that does several setState calls
reset() {
this.setState(prevState => ({value: prevState.initialValue}));
this.setState({initialValue: null});
}
Since we don't need access to state values in the second setState, we don't have to use functional setState. In this situation, does React still batch the functional setState at a later point in time (and potentially execute the object setState first)? Or would it maintain the execution order?
Both setState with a function argument (updater) and with an object argument will apply changes to the state object synchronously. The point is that it applies changes not to the this.state object but to the pending 'next' state object (this is my terminology, not from docs). And updater gives us the ability to access this pending 'next' state we're updating.
Consider this example:
// this.state.value === 0
this.setState({ value: this.state.value + 1 });
// pendingState.value === 1
this.setState(pendingState => ({ value: pendingState.value + 1 }));
// pendingState.value === 2
this.setState(pendingState => ({ value: pendingState.value + 2 }));
// pendingState.value === 4
this.setState({ value: this.state.value + 1 });
// pendingState.value === 1
Hopefully this helps. Same example in codesandbox: https://codesandbox.io/s/2p6j2mxnyj
I was checking the ReactJS tutorial at https://reactjs.org/docs/state-and-lifecycle.html and got confused at following point:
When we want to update the current state based on previous state we should invoke a variant of setState which takes a function
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
Now my question is who will invoke this function now with previous state ? As in if we call the setState directly with values we know that we have triggered the workflow to set the state to value. However in this case, who will invoke this method and how does it know what are the params to pass as this is a dynamic method ?
setState function will call the function.
For Example (Not he actual setState function just to show the concept)
var setState = function(param) {
var newState;
if(typeof param === 'function') {
newState = param(prevState); // run function that passed and get the returned object
// set new state with newState
}
else {
newState = param; // use passed object
}
// set new state with newState
}
setState(function(prevState) { return { some: 'Value' }; });
setState({ some: 'Value'});
The current signature of setState
setState({state_name : value})
setState(nextState, callback)
setState(callback)
There are some points to remember while using setState
There is no guarantee that this.state will be immediately updated, so
accessing this.state after calling this method may return the old
value.Because React may batch multiple setState() calls into a single update for performance.
e.g. Below one is wrong as there is no guarantee that this.state.counter is updated one.
assuming count=1 initially.
this.setState({
counter: this.state.counter + 1
});
this.setState({
counter: this.state.counter + 1
});
counter =1 and not 3 as
It’s safe to assume that setState is asynchronous.
To fix it, use other form of setState() that accepts a function(callback) rather than an object
When a function is provided to setState, React will be called it at some point in
the future (not synchronously). It will be called with the up to date
component arguments (state, props, context).
Using third signature
assuming count=1 initially.
this.setState((state)=>({counter: state.counter + 1}))
this.setState((state)=>({counter: state.counter + 1}))
counter = 3