usePrevious
2021-02-02
import { useEffect, useRef } from 'react';
/**
* Stores a reference to the previously rendered value.
*
* This can be used to execute dependency checks to compare whether a single
* dependency has changed.
*
* During the first render, `undefined` will be returned. Following renders will
* return a value.
*/
export const usePrevious = <T extends unknown>(value: T) => {
const ref = useRef<T | undefined>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};
Context
This hook can be really useful when you want to bail out of a useEffect
early. Or if you have a useEffect
with a few dependencies but you want to wait until a certain one has changed.
Technically, this hooks is kinda a "hack", I feel like it doesn't line up perfectly with React's data flow and programming model for hooks. I think in most cases there's a "better" way—something like flipping a flag based on the dependency that needs to update. But I've found myself reaching for usePrevious
in most of medium-to-large applications I've built the last few years.
How it works
usePrevious
starts of by setting up a useRef
in the first render. This will initially be undefined
(since in the first render there isn't a previous value).
Secondly, we set up a useEffect
to sync that ref
after each render cycle. useEffect
runs after each render is committed to the screen, so in the next render cycle, you'll have the previous render cycle's value stored in ref.current
Usage
const Component = ({ query, filter }) => {
const previousQuery = usePrevious(query);
useEffect(() => {
// If the query didn't change, bail out early
if (query !== previousQuery) return;
// Otherwise, continue fetching as usual.
fetchData(query, filter);
}, [query, previousQuery, filter]);
// render data
};
Tests
import { renderHook } from '@testing-library/react-hooks';
import { usePrevious } from './usePrevious';
test('should return the value of the previous render', () => {
let value = 1;
const { result, rerender } = renderHook(() => usePrevious(value));
expect(result.current).toEqual(undefined);
value = 2;
rerender();
expect(result.current).toEqual(1);
rerender();
expect(result.current).toEqual(2);
});