37

React introduced new static method getDerivedStateFromProps(props, state) which is called before every render method, but why? Calling it after prop change makes sense to me but after setState it doesn't, maybe I am missing something.

I was creating a datePicker Component according to my company requirement, in the component date is controlled from the prop. I have the following state in the component.

selectedDate: number;
selectedMonth: number;
selectedYear: number;
currentMonth: number;
currentYear: number;
view: string;

selected represents selected date which is derived from date prop and currentMonth and currentYear represents month and year in the current calendar view.

If date from prop changes selected*, currentMonth and currentYear should be changed accordingly. For that, I am using getDerivedStateFromProps but let say user clicks on Month name which will switch calendar view to month (instead of dates name of the month will be shown), the function updates the currentMonth for this using setState, but date the prop is same as before (containing previous month) which should, but getDerivedStateFromProps is called and currentMonth is again as same as before instead of changing.

Right I creating an extra variable in state to track if getDerivedStateFromProps is called due to setState but I don't think that's the right way.

Either I am doing something wrong or missing something or getDerivedStateFromProps should not be called after setState. Probably I am doing something wrong.

flppv
  • 4,111
  • 5
  • 35
  • 54
mukuljainx
  • 716
  • 1
  • 6
  • 16
  • getDerivedStateFromProps isn't called on setState call. Its when the parent re-renders that the childs getDerivedStateFromProps is called and when the component mounts. A reproducible demo or a relevant code might help in pointing out the mistake. Check this demo which proves that setState doesn't trigger getDerivedStateFromProps https://codesandbox.io/s/k94z83mz6r – Shubham Khatri Jun 25 '18 at 09:26
  • 11
    getDerivedStateFromProps is called before every render method, it was used to call only on prop change once but they changed that in a release probably 16.4 Can you check the sandbox again, I have updated the react and react-dom version – mukuljainx Jun 25 '18 at 09:35
  • 4
    Yeah, you are right, in the latest version it is called before every re-render – Shubham Khatri Jun 25 '18 at 09:38
  • 2
    So did you find a workaround for this. It seems to be useless, to call it on every rerender, as it breaks setState functionality, and it is impossible to distinguish between state change and props change. Really annoying... – tylik Dec 11 '18 at 21:07
  • 1
    Hi @tylik, I am setting state from props in the constructor and using componentDidUpdate to update state from props if required. – mukuljainx Dec 14 '18 at 20:47
  • @mukuljainx hi although officially they don't recommend this approach, it seems to be the only way to go. Thanks fro response! – tylik Dec 15 '18 at 21:38
  • @tylik, Yup, have that in mind :D. I will soon refactor the whole component with right approach few other things and will surely share it as an answer, right now I just don't have the time for it, many other tasks in the queue. – mukuljainx Jan 07 '19 at 09:19

6 Answers6

11

The way getDerivedStateFromProps hook works whenever the new props, setState, and forceUpdate is being received.

In the version of 16.3, React is not affecting getDerivedStateFromProps whenever the setState is being used. But they improved it in the version starting with 16.4, so whenever the setState is being called the getDerivedStateFromProps is being hooked.

Here's extracted image from React lifecycle diagram:

16.3

enter image description here

^16.4

enter image description here


So, it's up to you when to hook the getDerivedStateFromProps by checking props and states properly. Here's an example:

static getDerivedStateFromProps (props, state) {
  // check your condition when it should run?
  if(props.currentMonth != state.currentMonth) {
    return {
      currentMonth: state.currentMonth
    }
  }
  // otherwise, don't do anything
  else {
   return null
  }
}
Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231
11

I did something like this

constructor(props) {
    super(props);
    this.state = {
        expanded: props.expanded,
        ownUpdate: false
    }
}

static getDerivedStateFromProps(props, state) {
    if (state.ownUpdate) {
        return {
            expanded: state.expanded,
            ownUpdate: false
        };
    } else if (props.expanded !== state.expanded) {
        return {
            expanded: props.expanded
        };
    }
    return null;
}

toggle() {
    this.props.onAftePress(this.state.expanded, this.props.index);
    this.setState({
        expanded: !this.state.expanded,
        ownUpdate: true
    })
}
  • Is there any other solution for this in place of setting ownUpdate:true? – Vaibhav Agarwal Jun 03 '20 at 20:30
  • This does not look safe to me. React can batch rendering so it is possible for getDerivedStateFromProps to be called because your state changed **and** the props changed. In this particular case you're better off storing a prevExpanded state from the props.expanded value and comparing with that to see if the expanded prop has changed. – SystemParadox Jun 04 '21 at 10:53
3

I also got that issue. So I set another variable to check is that prop received for the first time.

this.state={flag:true}

In getderivedstatefromprops

static getderivedstatefromprops(props, state){
   if(props.<*propName*> && flag){
      return({ props.<*propName*>, flag:false})
   }
}

if you want to use multiple props values you need to set your if statements (or any other logic) accordingly.

Mahee Gamage
  • 413
  • 4
  • 12
0

you have the answer in your question itself. "which is called before every render method". Whenever you do a setState the render method will be called.

I would suggest that you lift the state variables currentMonth and currentYear to the parent component and pass them as prop along with other three. You can also pass the change handler as prop and call it from the child.

on initial render - currentMonth and currentYear can be set to null, so that you can have the logic for showing default stuff. When someone chicks on month name, you can call the changeHandler from parent which will pass the new prop. Now in getderivedstatefromprops you no longer have currentMonth and currentYear as `null so you know that the month has changed.

gehbiszumeis
  • 3,525
  • 4
  • 24
  • 41
Kamlesh Tajpuri
  • 482
  • 3
  • 11
0

For this case (updating the state based on props change), use:

componentDidUpdate(prevProps) {
  // don't forget to compare props
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

componentDidUpdate will get called after each update (due to props changes / state changes). so you should check if the prop is changed (by this.props.userID !== prevProps.userID).

yaya
  • 7,675
  • 1
  • 39
  • 38
0

The below seemed to work well for me, and is pretty general.

In constructor:

this.state = {
    props,
    // other stuff
}

In gDSFP:

static getDerivedStateFromProps(newProps, state) {
    if (state.props !== newProps)
        // Compute and return new state relevant to property change
    else
        return state;
}

A bit of experimentation confirms that "state" is no longer automatically the old state, but in this hook instance is the newly set state in combination with the existing props. I imagine the hook was added so as not to have to directly call gDSFP to repeat complex state evolutions dependent on the props, e.g.

this.setState(ClassName.getDerivedStateFromProps(this.props, newState))

The problem is that this is not quite a breaking change, but may result in subtle inefficiencies if old code needlessly does a lot of computation in getDerivedStateFromProps. I just discovered, for instance, that a complex object was being triply constructed, once for real, and two more times just because of this new hook.

Clint
  • 181
  • 1
  • 3