Why is using componentDidUpdate
more recommended over the setState
callback function (optional second argument) in React components (if synchronous setState behavior is desired)?
Since setState
is asynchronous, I was thinking about using the setState
callback function (2nd argument) to ensure that code is executed after state has been updated, similar to then()
for promises. Especially if I need a re-render in between subsequent setState
calls.
However, the official React Docs say "The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead." And that's all they say about it there, so it seems a bit vague. I was wondering if there was a more specific reason it is recommended to not use it? If I could I would ask the React people themselves.
If I want multiple setState calls to be executed sequentially, the setState callback seems like a better choice over componentDidUpdate in terms of code organization - the callback code is defined right there with the setState call. If I use componentDidUpdate I have to check if the relevant state variable changed, and define the subsequent code there, which is less easy to track. Also, variables that were defined in the function containing the setState call would be out of scope unless I put them into state too.
The following example might show when it might be tricky to use componentDidUpdate:
private functionInComponent = () => {
let someVariableBeforeSetStateCall;
... // operations done on someVariableBeforeSetStateCall, etc.
this.setState(
{ firstVariable: firstValue, }, //firstVariable may or may not have been changed
() => {
let secondVariable = this.props.functionFromParentComponent();
secondVariable += someVariableBeforeSetStateCall;
this.setState({ secondVariable: secondValue });
}
);
}
vs
public componentDidUpdate(prevProps. prevState) {
if (prevState.firstVariableWasSet !== this.state.firstVariableWasSet) {
let secondVariable = this.props.functionFromParentComponent();
secondVariable += this.state.someVariableBeforeSetStateCall;
this.setState({
secondVariable: secondValue,
firstVariableWasSet: false,
});
}
}
private functionInComponent = () => {
let someVariableBeforeSetStateCall = this.state.someVariableBeforeSetStateCall;
... // operations done on someVariableBeforeSetStateCall, etc.
this.setState({
firstVariable: firstValue,
someVariableBeforeSetStateCall: someVariableBeforeSetStateCall,
firstVariableWasSet: true });
//firstVariable may or may not have been changed via input,
//now someVariableBeforeSetStateCall may or may not get updated at the same time
//as firstVariableWasSet or firstVariable due to async nature of setState
}
Also, apart from componentDidUpdate being generally recommended, in what cases would the setState callback be more appropriate to use?
What is the purpose of the callback function as an argument of setState () in react? Start writing here...The callback function is invoked when setState finished and the component gets rendered. Since setState is asynchronous the callback function is used for any post action.
When using the callback argument to setState (), you might have two separate calls to setState () in different places which both update the same state, and you'd have to remember to use the same callback in both places. A common example is making a call to a third-party service whenever a piece of state changes:
Let’s see how to perform a callback inside a React class component after setState executes: This nifty drinking age checker component displays a single input. After changing the value inside that input, it changes the age value inside of its state. Focus in on the checkAge function. That’s where the setState function gets called.
The componentDidUpdate ()is called after componentDidMount () and can be useful to perform some action when the state of the component changes. Parameters: Following are the parameter used in this function: Tip: To avoid an infinite loop, all the network requests are needed to be inside a conditional statement as:
Why is using
componentDidUpdate
more recommended over thesetState
callback function?
When using the callback argument to setState()
, you might have two separate calls to setState()
in different places which both update the same state, and you'd have to remember to use the same callback in both places.
A common example is making a call to a third-party service whenever a piece of state changes:
private method1(value) {
this.setState({ value }, () => {
SomeAPI.gotNewValue(this.state.value);
});
}
private method2(newval) {
this.setState({ value }); // forgot callback?
}
This is probably a logic error, since presumably you'd want to call the service any time the value changes.
This is why componentDidUpdate()
is recommended:
public componentDidUpdate(prevProps, prevState) {
if (this.state.value !== prevState.value) {
SomeAPI.gotNewValue(this.state.value);
}
}
private method1(value) {
this.setState({ value });
}
private method2(newval) {
this.setState({ value });
}
This way, the service is guaranteed to be called whenever the state updates.
Additionally, state could be updated from external code (e.g. Redux), and you won't have a chance to add a callback to those external updates.
The callback argument of setState()
executes after the component is re-rendered. However, multiple calls to setState()
are not guaranteed to cause multiple renders, due to batching.
Consider this component:
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { value: 0 };
}
componentDidUpdate(prevProps, prevState) {
console.log('componentDidUpdate: ' + this.state.value);
}
onClick = () => {
this.setState(
{ value: 7 },
() => console.log('onClick: ' + this.state.value));
this.setState(
{ value: 42 },
() => console.log('onClick: ' + this.state.value));
}
render() {
return <button onClick={this.onClick}>{this.state.value}</button>;
}
}
We have two setState()
calls in the onClick()
handler, each simply prints the new state value to the console.
You might expect onClick()
to print the value 7
and then 42
. But actually, it prints 42
twice! This is because the two setState()
calls are batched together, and only cause one render to occur.
Also, we have a componentDidUpdate()
which also prints the new value. Since we only have one render occurring, it is only executed once, and prints the value 42
.
If you want consistency with batched updates, it's usually far easier to use componentDidMount()
.
It doesn't matter.
Batching is an optimization, and therefore you should never rely either on batching occurring or it not occurring. Future versions of React may perform more or less batching in different scenarios.
But, if you must know, in the current version of React (16.8.x), batching occurs in asynchronous user event handlers (e.g. onclick
) and sometimes lifecycle methods if React has full control over the execution. All other contexts never use batching.
See this answer for more info: https://stackoverflow.com/a/48610973/640397
setState
callback?When external code needs to wait for the state to be updated, you should use the setState
callback instead of componentDidUpdate
, and wrap it in a promise.
For example, suppose we have a Child
component which looks like this:
interface IProps {
onClick: () => Promise<void>;
}
class Child extends React.Component<IProps> {
private async click() {
await this.props.onClick();
console.log('Parent notified of click');
}
render() {
return <button onClick={this.click}>click me</button>;
}
}
And we have a Parent
component which must update some state when the child is clicked:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
}
private setClicked = (): Promise<void> => {
return new Promise((resolve) => this.setState({ clicked: true }, resolve));
}
render() {
return <Child onClick={this.setClicked} />;
}
}
In setClicked
, we must create a Promise
to return to the child, and the only way to do that is by passing a callback to setState
.
It's not possible to create this Promise
in componentDidUpdate
, but even if it were, it wouldn't work properly due to batching.
Since
setState
is asynchronous, I was thinking about using thesetState
callback function (2nd argument) to ensure that code is executed after state has been updated, similar to.then()
for promises.
The callback for setState()
doesn't quite work the same way as promises do, so it might be best to separate your knowledge.
Especially if I need a re-render in between subsequent
setState
calls.
Why would you ever need to re-render a component in between setState()
calls?
The only reason I can imagine is if the parent component depends on some info from the child's DOM element, such as its width or height, and the parent sets some props on the child based on those values.
In your example, you call this.props.functionFromParentComponent()
, which returns a value, which you then use to compute some state.
Firstly, derived state should be avoided, since memoization is a much better option. But even so, why not just have the parent pass the value directly down as a prop? Then you can at least compute the state value in getDerivedStateFromProps()
.
//firstVariable may or may not have been changed,
//now someVariableBeforeSetStateCall may or may not get updated at the same time
//as firstVariableWasSet or firstVariable due to async nature of setState
These comments don't make much sense to me. The asynchronous nature of setState()
doesn't imply anything about state not getting properly updated. The code should work as intended.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With