Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the advantage of using componentDidUpdate over the setState callback?

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?

like image 526
bitangmi Avatar asked Jun 07 '19 21:06

bitangmi


People also ask

What is the purpose of callback function as an argument of setState ()?

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.

Can I use setState () in multiple places with the same callback?

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:

How to callback inside a react class component after setState executes?

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.

What is the use of componentdidupdate ()?

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:


1 Answers

Why is using componentDidUpdate more recommended over the setState callback function?

1. Consistent logic

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.

2. Batched 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().

2.1. When does batching occur?

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

3. When is it better to use the 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.

Misc.

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.

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.

like image 151
Apples Avatar answered Oct 22 '22 11:10

Apples