React: why we have callback in setState? - reactjs

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.

Related

How does React batch setState when mixing object and callback approach?

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

Using nextProps in setState updater

Is there a possibility to use the nextProps arg in a setState called in componentWillReceiveProps ?
From the React documentation the signature is defined as follows:
setState((prevState, props) => stateChange[, callback])
so that I don't see how it is possible to use nextProps other than using the shallow merge:
setState(stateChange[, callback])
for instance:
componentWillReceiveProps(nextProps) {
this.setState({
data: nextProps.data
}, myCallBack)
}
However this latter is not well suited for multiple setState so that I don't want/I can't use it.
This should work like you are expecting. Although, since you are reading from props and not state, multiple setState calls shouldn't be an issue.
componentWillReceiveProps(nextProps) {
this.setState( prevState => {
return { data: nextProps.data }
}, myCallBack)
}
You can update your state in multiple ways:
this.setState(() => {
return {
data: nextProps.data
}
});
or
this.setState({
data: nextProps.data
});
It will be the same since you're not updating based on your state, but on the nextProps. You can also do multiple setState calls and if they are in the same render cycle, React can and will batch these updates for you.

ReactJs - state is rendered properly but inside method, shows wrong value

componentWillMount() {
console.log(this.state);
this.setState({
...this.state,
counter: this.state.counter + 1
});
console.log(this.state);
}
render() {
return (
{this.state.counter}
);
}
The output is clearly 1 in the screen
But both console.log is outputting 0.
I was doing a quiz app in reactjs. Same problem happened. Answered, correct Answers are always one less than the real values.
Calling setState doesn't immediately mutate the state.
Check the docs: https://reactjs.org/docs/react-component.html#setstate
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
To really check the updated state, you can do the following:
this.setState(Object.assign({}, this.state, {
counter: this.state.counter + 1
}), function () {
console.log(this.state.counter);
});
Also as per the documentation here: https://reactjs.org/docs/react-component.html#componentwillmount
componentWillMount() is invoked immediately before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering. Generally, we recommend using the constructor() instead.
Avoid introducing any side-effects or subscriptions in this method. For those use cases, use componentDidMount() instead.
the initial changes to state should be done in constructor as componentWillMount won't re-render your component. Or you are better off with componentDidMount.
I hope this helps.
setState is asynchronous , so there is no guarantee that you will see updated state value on the next line. If you want to make sure that the new value reflect in second console.
componentWillMount() {
console.log(this.state);
this.setState({
...this.state,
counter: this.state.counter + 1
}, () => {
console.log(this.state);
});
}
render() {
return (
{this.state.counter}
);
}
Your state is being changed for the first time before the component has been mount. After that the state isn't changed again so the render isn't triggered and therefore your counter isn't updated.
Try with componentDidMount instead.

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

When to use React setState callback

When a react component state changes, the render method is called. Hence for any state change, an action can be performed in the render methods body. Is there a particular use case for the setState callback then?
Yes there is, since setState works in an asynchronous way. That means after calling setState the this.state variable is not immediately changed. so if you want to perform an action immediately after setting state on a state variable and then return a result, a callback will be useful
Consider the example below
....
changeTitle: function changeTitle (event) {
this.setState({ title: event.target.value });
this.validateTitle();
},
validateTitle: function validateTitle () {
if (this.state.title.length === 0) {
this.setState({ titleError: "Title can't be blank" });
}
},
....
The above code may not work as expected since the title variable may not have mutated before validation is performed on it. Now you may wonder that we can perform the validation in the render() function itself but it would be better and a cleaner way if we can handle this in the changeTitle function itself since that would make your code more organised and understandable
In this case callback is useful
....
changeTitle: function changeTitle (event) {
this.setState({ title: event.target.value }, function() {
this.validateTitle();
});
},
validateTitle: function validateTitle () {
if (this.state.title.length === 0) {
this.setState({ titleError: "Title can't be blank" });
}
},
....
Another example will be when you want to dispatch and action when the state changed. you will want to do it in a callback and not the render() as it will be called everytime rerendering occurs and hence many such scenarios are possible where you will need callback.
Another case is a API Call
A case may arise when you need to make an API call based on a particular state change, if you do that in the render method, it will be called on every render onState change or because some Prop passed down to the Child Component changed.
In this case you would want to use a setState callback to pass the updated state value to the API call
....
changeTitle: function (event) {
this.setState({ title: event.target.value }, () => this.APICallFunction());
},
APICallFunction: function () {
// Call API with the updated value
}
....
this.setState({
name:'value'
},() => {
console.log(this.state.name);
});
The 1. usecase which comes into my mind, is an api call, which should't go into the render, because it will run for each state change. And the API call should be only performed on special state change, and not on every render.
changeSearchParams = (params) => {
this.setState({ params }, this.performSearch)
}
performSearch = () => {
API.search(this.state.params, (result) => {
this.setState({ result })
});
}
Hence for any state change, an action can be performed in the render methods body.
Very bad practice, because the render-method should be pure, it means no actions, state changes, api calls, should be performed, just composite your view and return it. Actions should be performed on some events only. Render is not an event, but componentDidMount for example.
Consider setState call
this.setState({ counter: this.state.counter + 1 })
IDEA
setState may be called in async function
So you cannot rely on this. If the above call was made inside a async function this will refer to state of component at that point of time but we expected this to refer to property inside state at time setState calling or beginning of async task. And as task was async call thus that property may have changed in time being. Thus it is unreliable to use this keyword to refer to some property of state thus we use callback function whose arguments are previousState and props which means when async task was done and it was time to update state using setState call prevState will refer to state now when setState has not started yet. Ensuring reliability that nextState would not be corrupted.
Wrong Code: would lead to corruption of data
this.setState(
{counter:this.state.counter+1}
);
Correct Code with setState having call back function:
this.setState(
(prevState,props)=>{
return {counter:prevState.counter+1};
}
);
Thus whenever we need to update our current state to next state based on value possed by property just now and all this is happening in async fashion it is good idea to use setState as callback function.
I have tried to explain it in codepen here CODE PEN
Sometimes we need a code block where we need to perform some operation right after setState where we are sure the state is being updated. That is where setState callback comes into play
For example, there was a scenario where I needed to enable a modal for 2 customers out of 20 customers, for the customers where we enabled it, there was a set of time taking API calls, so it looked like this
async componentDidMount() {
const appConfig = getCustomerConfig();
this.setState({enableModal: appConfig?.enableFeatures?.paymentModal }, async
()=>{
if(this.state.enableModal){
//make some API call for data needed in poput
}
});
}
enableModal boolean was required in UI blocks in the render function as well, that's why I did setState here, otherwise, could've just checked condition once and either called API set or not.

Resources