DEV Community

Cover image for React Custom Hooks for prevProps & prevState
Dane David
Dane David

Posted on

React Custom Hooks for prevProps & prevState

For long, we we used to write code like this inside of our React components:

componentDidUpdate(prevProps, prevState) {
  // did 'count' increase from being 0?
  if ( prevProps.count === 0 && this.props.count > 0 ) {
    // do something
  }
}

Sometimes, you may not just want to know if a piece of prop/state changed, but also what its previous value was. It was easier in the lifecycle-methods-universe, but not so straight forward with functional components.

Today I'm going to share a simple, callback-based custom hook that can serve as a replacement for the code above.

I call the hook usePrevValues, and its API looks like:

const MyComponent = ({ count }) => {
  usePrevValues(
    {
      count,
    },
    prevValues => {
      if (prevValues.count === 0 && count > 0) {
        // do the same thing here
      }
    }
  );

  return (
    /* JSX stuff */
  );
};

As you can see, the first argument is a plain object, whose key-value pairs are all the values from props & state (and even values derived from those) for which you want to track the previous values. The second argument is a callback function, which receives an object of previous values as argument.

Breakdown

This hook works because of the useRef hook.

From the docs:

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

Combining this with the more popular useEffect hook solves our problem: After each render, just before the effect is run, save the values to a ref value. React API provides such a point in the render cycle, through the "cleanup" method returned from the useEffect callback. And then, invoke the callback from inside the effect, with the ref object as the argument.

The body of our custom hook is as follows:

Follow Up:

A keen eye would notice that, though we can compare previous & next values and exclusively run code, our callback function gets executed in every render. This is because, on using the useEffect hook, we missed out the dependency array to prevent it from running after all renders. So we'll add it:

But is this going to solve our problem? No. Because, during each render, we're passing a new object literal as value and a new function reference as callback. Fortunately, React has solutions for both: useMemo and useCallback. Thus, to use the above hook without unnecessary invokations of the callback, we write it as follows:

That's it! It's wonderful how, with React hooks, solutions form automagically by combining the basic hooks.

I hope the above write-up made sense, and was easy to grasp. If not, or if you liked this post & would like to let me know, drop a comment below!


Also you can find me on Twitter - @this_dane, or on Github - danedavid.
Stay safe!

Top comments (0)