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)