When to use setState callback argument versus directly passing the value - reactjs

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.

Related

Why need to pass a callback accepting the previous state?

class ClassCounterTwo extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
incrementCount = () => {
this.setState(prevState => {
return {
count: prevState.count + 1
}
})
}
render() {
return (
<div>
<button onClick={this.incrementCount}>Count {this.state.count}</button>
</div>
)
}
When updating state based on previous state, why does React make mistakes often if you don't pass a callback accepting the previous state like this:
this.setState(prevState => {
return {
count: prevState.count + 1
}
Sorry, just asking the question because I just saw somewhere the founder of Node.js said, "Try to push yourself to understand the system."
why does React make mistakes often if you don't pass a callback accepting the previous state
When you set state, the component will rerender very soon, but not synchronously. This lets react batch up multiple changes and apply them all at once, so that only one render is needed when state is set multiple times in the same call stack.
So if you wrote two lines of code back to back like this:
this.setState({ count: 1 });
this.setState({ count: 2 });
It will only render once, with the 2 as the new state.
The problem comes if you write code that checks this.state with the assumption that this.state is up to date. For example:
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
If count was at 0 when this code began, then i will be telling react to set the count to 1, and then telling react to set it to 1 again, since this.state.count didn't change in between these two lines. React will only render once, but since i told it 1 both times, that's what we'll be left with on the screen.
In most cases, these lines won't appear back to back in your code; they may be executing on different event listeners in different parts of your component, but in any event, the core point is that this.state.count only tells you what the count was when your code is executing, not what it is when we're just about to render.
So if you need to base your new state on the old one, there's the function version of setState. React will call the function and be sure to pass in the most recent value, letting you calculate the next state correctly no matter how many times you're updating it.

React Asynchronous State Updates setState argument (function vs non-function)

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

reactjs why use spread operator in setState

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 }

ReactJS difference between setting state directly and through method argument

I have started learning ReactJS and realize that there are two way I can change the state. Both works fine without any error or warning.
What is the main difference? is there a reason or need I should use second version?
consider I want to modify following state on each click
this.state = { count: 0 }
First Version
handleClick = () => {
this.setState({count: this.state.count+1})
}
Second Version
handleClick = () => {
this.setState(prvState => {
return {
count: prvState+1
}
})
}
If you are setting the next state value based on the previous state
value then passing in a function (second version) as the first
parameter of this.setState instead of an object is the recommended
solution.
handleClick = () => {
this.setState(prvState => {
return {
count: prvState+1
}
})
}
The reason is that this.state may be updated asynchronously and that's why you should not rely on their values for calculating the next state.
In the first version, there's a high possibility that the value of count is incorrect especially once your application gets bigger.
If you rely on the previous state (like in your case you need the previous counter value), the second approach is preferred. It guarantees that prvState holds the current state. This is important if you have two setState modifying the state in the same render loop (remember that React may batch multiple setState calls together), e.g.
this.setState({count: this.state.count+1})
this.setState({count: this.state.count+1})
has the problem that the count gets only incremented once. While
this.setState((prevState) => {count: prevState.count+1})
this.setState((prevState) => {count: prevState.count+1})
guarantees that it gets incremented twice as intended (independent of the order the setState are handled by React)

setState implementation in Reactjs

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

Resources