componentWillReceiveProps, componentDidUpdate for React Hook

JavascriptReactjsReact Hooks

Javascript Problem Overview


I run into two challenges:

  • Even if, as per React guideline, derived state is discouraged, but some edge cases still need it.
    In terms of a functional component with React Hook, What is the equivalent implementation with React Hook, If I do need derived state which in class component, will be updated in componentWillReceiveProps on every parent render

see below code sample:

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: props.count > 100 ? 100 : props.count,
    }

  }

  /*What is the equivalent implementation when React Hook is used here componentWillReceiveProps*/
  componentWillReceiveProps(nextProps) {
    if (nextProps.count !== this.props.count) {
      this.setState({
        count: nextProps.count > 100 ? 100 : nextProps.count
      });
    }
  }

  render() {
    return ( <
      div > {
        this.state.count
      } <
      /div>
    );
  }
}

export default App;

  • As for componentDidUpdate, componentDidUpdate has its counterpart when React Hook is used, you have to use it like,

      React.useEffect(() => {
        return () => {
          
         };
      }, [parentProp]);
    

    the Second param for useEffect makes sure code is executed only when prop changes, but what if I want to do respective tasks based on multiple respective props changes? how to get it done with useEffect?

see below code sample:

class App extends Component {


  /*What is the equivalent implementation when functional component with React Hook is used here */
  componentDidUpdate(prevProps, prevState) {
    if (prevProps.groupName !== this.props.groupName) {
      console.log('Let'
        's say, I do want to do some task here only when groupName differs');
    } else if (prevProps.companyName !== this.props.companyName) {
      console.log('Let'
        's say, I do want to do some different task here only when companyName differs');
    }

  }


  render() {
    /*for simplicity, render code is ignored*/
    return null;
  }
}

export default App;

Javascript Solutions


Solution 1 - Javascript

The react hook equivalent to the old componentWillReceive props can be done using the useEffect hook, just specifying the prop that we want to listen for changes in the dependency array.

I.e:

export default (props) => {

    useEffect( () => {
        console.log('counter updated');
    }, [props.counter])

    return <div>Hi {props.counter}</div>
}

For componentDidUpdate just by omitting the dependency array, the useEffect function will be called after every re-render.

I.e:

export default (props) => {

    useEffect( () => {
        console.log('counter updated');
    })

    return <div>Hi {props.counter}</div>
}

Solution 2 - Javascript

You can use the useMemo hook to store a calculation and put props.count in the array given as second argument to recalculate the value when it changes.

const { useState, useEffect, useMemo } = React;

function App() {
  const [count, setCount] = useState(50);

  useEffect(() => {
    setTimeout(() => {
      setCount(150);
    }, 2000);
  }, []);

  return <DisplayCount count={count} />;
}

function DisplayCount(props) {
  const count = useMemo(() => props.count > 100 ? 100 : props.count, [props.count]);

  return <div> {count} </div>;
}

ReactDOM.render(<App />, document.getElementById("root"));

<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>

The easiest way to do separate effects when separate props change is to create multiple useEffect hooks that are only run when one of the separate props change.

const { useState, useEffect } = React;

function App() {
  const [groupName, setGroupName] = useState('foo');
  const [companyName, setCompanyName] = useState('foo');

  useEffect(() => {
    setTimeout(() => {
      setGroupName('bar');
    }, 1000);
    setTimeout(() => {
      setCompanyName('bar');
    }, 2000);
  }, []);

  return <DisplayGroupCompany groupName={groupName} companyName={companyName} />;
}

function DisplayGroupCompany(props) {
  useEffect(() => {
    console.log("Let's say, I do want to do some task here only when groupName differs");
  }, [props.groupName])
  useEffect(() => {
    console.log("Let's say,I do want to do some different task here only when companyName differs");
  }, [props.companyName])

  return <div> {props.groupName} - {props.companyName} </div>;
}

ReactDOM.render(<App />, document.getElementById("root"));

<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Solution 3 - Javascript

If you use the useMemo hook on top of your component and have it dependent on all your props, it runs before everything everytime props change. useEffect is triggered after the updated render and since dependent on all props it triggers after a rerender depending on all props.

const Component = (...props) => {
   // useState, useReducer if have
   useMemo(() => {
     // componentWillReceiveProps
   },[...props]);
   // ...other logic and stuff
   useEffect(() => {
     // componentDidUpdate
   }, [...props]);
};

Solution 4 - Javascript

setCount will trigger a re-render. Using useEffect with [count] as the dependencies array will ensure that the hook will only calls setCount when count's value changes.

This is how you go about replacing whatever componentWillReceiveProps logic you might have otherwise written in the old class-based React style. I find the "Each Render Has Its Own Props and State" principle useful: if you want to trigger a re-render only when specific props change, you can have several useEffect hooks.

useEffect(() => {
  count > 100 ? setCount(100) : setCount(count)
}, [count]) 
 
useEffect(() => {
  console.log('groupName has changed');
  // do something with groupName
}, [groupName])
    
useEffect(() => {
  console.log('companyName has changed');
  // do something with companyName
}, [companyName]) 

Solution 5 - Javascript

In your scenario, you don't need to use or re-implement getDerivedStateFromProps at all. You just need to create a new variable to get the new form of data. Using state in this scenario will just cause another re-rendering which is not good performance wise.

import React from 'react';

const App = ({ count }) => {
  const derivedCount = count > 100 ? 100 : count;

  return (
    <div>Counter: {derivedCount}</div>
  );
}

App.propTypes = {
  count: PropTypes.number.isRequired
}

Demo here: https://codesandbox.io/embed/qzn8y9y24j?fontsize=14

You can read more on different ways to solve these kind of scenarios without using getDerivedStateFromProps here: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

If you really need to use a separate state, you can use something like this

import React, { useState } from 'react';

const App = ({ count }) => {
  const [derivedCounter, setDerivedCounter] = useState(
    count > 100 ? 100 : count
  );

  useEffect(() => {
    setDerivedCounter(count > 100 ? 100 : count);
  }, [count]); // this line will tell react only trigger if count was changed

  return <div>Counter: {derivedCounter}</div>;
};

Solution 6 - Javascript

simply by using useEffect like this.

useEffect( () => {
    props.actions.fetchSinglePost(props.match.params.id); //> I'm dispatching an action here.
}, [props.comments]) //> and here to watch comments and call the action in case there is any change.

Solution 7 - Javascript

In a case, you want to replicate a log system such as why did you render, and you need the nextProps here is helping hook.

  • aligned with old lifeCycle function rename stuffs to props and nextProps
  • nextProps means current props
  • props mean previous props
const useComponentWillReceiveProps(nextProps, callback) {
  const props = useRef(nextProps) 

  useEffect(() => {
    callback(nextProps, props.current)
    props.current = nextProps
  })
}

Usage

const diffLogger = (nextProps, props, { name = "Component" } = {}) => {
  Object.keys(nextProps)
    .filter((key) => nextProps[key] !== props[key])
    .forEach((key) => {
      console.log(
        `%c${name}:%c${key}`,
        "color:#ff5722; font-size:1rem; font-weight:bold;",
        "color:#ffc107; font-size:1.2rem; font-weight:bold;",
        {
          from: props[key],
          to: nextProps[key],
        }
      )
    })
}
const Button = (props) => {
 useComponentWillReceiveProps(props, diffLogger, {name: 'Button'})
 return <button onClick={props.onClick}>Button</button>
}

Now If your button re-render e.g. by onClick new reference you will get something like this: enter image description here

Solution 8 - Javascript

I realize your "derived state" example is intentionally simple, but because there are so few legitimate cases of derived state, it is difficult to make a recommendation on the replacement except on a case-by-case basis since it depends on the reason you are using derived state. In the particular example you provided, there was no reason to use derived state in the class case and so there continues to be no reason in the hook case (the value can just be derived locally without putting it in state). If the derived value is expensive, you can use useMemo as Tholle presents. If these don't fit the more realistic case(s) you have in mind, you would need to present a more specific case that truly requires derived state.

As far as your componentDidUpdate example, if what you want to do for the different props is independent, then you can use separate effects for each (i.e. multiple useEffect calls). If you want to do exactly what is in your example (i.e. only do something for a companyName change if groupName didn't also change as indicated by your else if), then you can use refs for more sophisticated conditions. You should not mutate the ref during rendering (there is always the possibility of the render being discarded/redone once concurrent mode is supported), so the example uses the last effect to make updates to the refs. In my example, I use a ref to avoid doing effect work on the initial render (see Tholle's answer in this related question) and to detect whether or not groupName changed when deciding whether or not to do work based on a companyName change.

const { useState, useEffect, useRef } = React;

const DerivedStateFromProps = ({ count }) => {
  const derivedCount = count > 100 ? 100 : count;

  return (
    <div>
      Derived from {count}: {derivedCount}{" "}
    </div>
  );
};
const ComponentDidUpdate = ({ groupName, companyName }) => {
  const initialRender = useRef(true);
  const lastGroupName = useRef(groupName);
  useEffect(
    () => {
      if (!initialRender.current) {
        console.log("Do something when groupName changes", groupName);
      }
    },
    [groupName]
  );
  useEffect(
    () => {
      if (!initialRender.current) {
        console.log("Do something when companyName changes", companyName);
      }
    },
    [companyName]
  );
  useEffect(
    () => {
      if (!initialRender.current && groupName === lastGroupName.current)
        console.log(
          "Do something when companyName changes only if groupName didn't also change",
          companyName
        );
    },
    [companyName]
  );
  useEffect(
    () => {
      // This effect is last so that these refs can be read accurately in all the other effects.
      initialRender.current = false;
      lastGroupName.current = groupName;
    },
    [groupName]
  );

  return null;
};
function App() {
  const [count, setCount] = useState(98);
  const [groupName, setGroupName] = useState("initial groupName");
  const [companyName, setCompanyName] = useState("initial companyName");
  return (
    <div>
      <div>
        <DerivedStateFromProps count={count} />
        <button onClick={() => setCount(prevCount => prevCount + 1)}>
          Increment Count
        </button>
      </div>
      <div>
        <ComponentDidUpdate groupName={groupName} companyName={companyName} />
        groupName:{" "}
        <input
          type="text"
          value={groupName}
          onChange={event => setGroupName(event.target.value)}
        />
        <br />
        companyName:{" "}
        <input
          type="text"
          value={companyName}
          onChange={event => setCompanyName(event.target.value)}
        />
        <br />
        change both{" "}
        <input
          type="text"
          onChange={event => {
            const suffix = event.target.value;
            setGroupName(prev => prev + suffix);
            setCompanyName(prev => prev + suffix);
          }}
        />
      </div>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

Edit Derived state and componentDidUpdate

Solution 9 - Javascript

> 1.) What is the equivalent implementation with React Hook, If I do need derived state?

Derived state for Hooks = set state conditionally and directly in the render phase:

constComp = (props) => {
  const [derivedState, setDerivedState] = useState(42);
  if (someCondition) {
    setDerivedState(...);
  }
  // ...
}

This updates state without an additional commit phase as opposed to useEffect. Above pattern is supported by React Strict Mode (no warnings):

const App = () => {
  const [row, setRow] = React.useState(1);

  React.useEffect(() => {
    setTimeout(() => {
      setRow(2);
    }, 3000);
  }, []);

  return (
    <React.StrictMode>
      <Comp row={row} />
    </React.StrictMode>
  );
}

const Comp = ({ row }) => {
  const [isScrollingDown, setIsScrollingDown] = React.useState(false);
  const [prevRow, setPrevRow] = React.useState(null);

  console.log("render, prevRow:", prevRow)

  if (row !== prevRow) {
    console.log("derive state");
    // Row changed since last render. Update isScrollingDown.
    setIsScrollingDown(prevRow !== null && row > prevRow);
    setPrevRow(row);
  }

  return `Scrolling down: ${isScrollingDown}`;
};

ReactDOM.render(<App />, document.getElementById("root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js" integrity="sha256-4gJGEx/zXAxofkLPGXiU2IJHqSOmYV33Ru0zw0TeJ30=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.min.js" integrity="sha256-9xBa2Hcuh2S3iew36CzJawq7T9iviOAcAVz3bw8h3Lo=" crossorigin="anonymous"></script>
<div id="root"></div>

Note 1: componentWillReceiveProps has been deprecated for quite some time. getDerivedStateFromProps is the class components' successor in terms of derived state.

Note 2: Check preferred solutions before you resort to derived state.


> 2.) What if I want to do respective tasks based on multiple respective props changes?

You can either leave useEffect deps out completely or preferably add another prop dep:

React.useEffect(() => {
  return () => { };
}, [parentProp, secondProp]);

Solution 10 - Javascript

I'm using React Native. I added this to my screen component where I needed a componentWillReceiveProps functionality:

    const propsRef = useRef(props)

	// component will receive props
	const componentWillReceiveProps = () => {
		const propsHaveChanged = !isEqual(propsRef.current, props)

        propsRef.current = props

		if (propsHaveChanged){
			setIndex(0)
		}

        
	}

Here isEqual is a lodash function that does a deep comparison. Then just call the function.

componentWillReceiveProps()

This is called at the top of the functional component. So, it should be called before the functional component renders it's view

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDeng ZhebinView Question on Stackoverflow
Solution 1 - JavascriptfgonzalezView Answer on Stackoverflow
Solution 2 - JavascriptTholleView Answer on Stackoverflow
Solution 3 - JavascriptjpmarksView Answer on Stackoverflow
Solution 4 - JavascriptThien LyView Answer on Stackoverflow
Solution 5 - JavascriptKevin F.View Answer on Stackoverflow
Solution 6 - JavascriptHasan ZahranView Answer on Stackoverflow
Solution 7 - JavascriptAmirheView Answer on Stackoverflow
Solution 8 - JavascriptRyan CogswellView Answer on Stackoverflow
Solution 9 - Javascriptford04View Answer on Stackoverflow
Solution 10 - JavascriptJo MommaView Answer on Stackoverflow