Robin van der Vleuten

Use previous value through a React hook

React Hooks took me a little while to get used to after they were introduced in React v16.8.0. Once they clicked, the appeal was obvious: custom hooks let you move repeated component logic into small functions.

A few apps in, I had a small library of patterns I reuse often.

One of those patterns is reading the previous value of state or props. It only takes a few lines and uses useRef(). A ref can point at an HTML element, but it can also store any value you assign to it.

Combined with useEffect(), that gives us a small usePrevious() hook:

import React from 'react';

export default function usePrevious<T>(value: T): T {
  // Create a reference to hold the previous version of the value,
  // as it is basically a generic object whose `current` property can hold any value.
  const ref = React.useRef<T>();

  // Use the `useEffect` hook to run a callback...
  React.useEffect(() => {
    // ...to store the passed value on the ref's current property...
    ref.current = value;
  }, [value]); // ...whenever the value changes.

  // And return the currently stored value,
  // as this will run before the `useEffect` callback runs.
  return ref.current;
}

What to do with this hook?

When you combine usePrevious() with state, you can run logic when a value differs from the previous render. I often use it to compare boolean values, like a loading state:

import React from 'react';

const Application = () => {
  // Call some external API through a custom hook.
  const { data, loading } = useAsyncApi()
  // Pass its loading indicator to our created hook.
  const wasLoading = usePrevious<boolean>(loading);

  // Use the `useEffect` hook to run a callback...
  React.useEffect(() => {
    // ...to check if the API was loading but now it is completed...
    if (!loading && wasLoading) {
        // ...and run some arbitary code...
    }
  }, [loading, wasLoading]); // ...whenever one of these changes.

  return (
    <div>
      {/* ... */}
    </div>
  );
}

It is a small hook, but it is useful whenever the current render needs to know what changed since the previous one.