Telerik blogs

Learn how to use refs to store the previous values of state and props in React since there React doesn’t have a built-in way to access this value.

Sometimes we want to access the value of a previous prop or state value. React does not provide a built-in way to do this, so let’s explore how to do this with refs.

Use Refs to Store Old Reactive Values

In React, refs are non-reactive values in components and hooks. We can use this to store the values of things that we want to last between re-renders and won’t trigger a re-render when their values change.

To define a ref, we can use the useRef hook.

For instance, we write:

import { useRef } from "react";

export default function App() {
  const ref = useRef();
  ref.current = 100;
  console.log(ref.current);

  return <div className="App"></div>;
}

to define the ref ref with the React built in useRef hook.

And we set its value by setting ref.current to 100. We can put the ref.current value assignment in the root level of the component since it won’t trigger a re-render of the component when we assign a value to it. And the value will be kept as long as the component is mounted.

These characteristics of refs make them good for keeping the previous values of React reactive values.

To use refs to store the previous values of states, we can assign those values to refs. For instance, we write:

import { useRef, useState } from "react";

export default function App() {
  const prevCountRef = useRef();
  const [count, setCount] = useState(0);

  const onClick = () => {
    prevCountRef.current = count;
    setCount((c) => c + 1);
  };
  console.log(prevCountRef.current, count);

  return (
    <div className="App">
      <button onClick={onClick}>increment</button>
      <div>{count}</div>
    </div>
  );
}

to define the count state with the useState hook. And we define the prevCountRef ref with the useRef hook.

Next, we define the onClick function that sets the prevCountRef to the count value before we call setCount to increment the count state value by 1.

Then we add a button to call onClick when we click on the button to update both values. As a result, we see from the console log that prevCountRef.current is always the previous count value and count having the latest value.

Likewise, we can store the previous values of props the same way since props are also reactive values.

To do the same thing with props, we write:

import { useEffect, useRef, useState } from "react";

const CountDisplay = ({ count }) => {
  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;
  }, [count]);
  console.log(prevCountRef.current, count);
  return <div>{count}</div>;
};

export default function App() {
  const [count, setCount] = useState(0);

  const onClick = () => {
    setCount((c) => c + 1);
  };

  return (
    <div className="App">
      <button onClick={onClick}>increment</button>
      <CountDisplay count={count} />
    </div>
  );
}

to define the CountDisplay component.

In it, we take the count prop and assign it as the value of the prevCountRef value as the count value changes.

We can do that by putting prevCountRef.current = count; in the useEffect hook callback and make the hook watch the count value.

This can keep the previous count prop value since it won’t trigger a re-render until the count prop gets updated.

As a result, we see from the console log that prevCountRef.current is always the previous count value and count having the latest value as we do with the previous example.

Extract Logic Into a Hook

To make the logic we have for storing previous props or states reusable, we can move the logic into its own hook.

To do this, we make the hook accept the reactive prop or state value and return the previous and latest reactive variable values.

For instance, we can write our own hook by writing:

import { useEffect, useRef, useState } from "react";

const usePrevious = (value) => {
  const prevValueRef = useRef();
  useEffect(() => {
    prevValueRef.current = value;
  }, [value]);

  return { prev: prevValueRef.current, current: value };
};

export default function App() {
  const [count, setCount] = useState(0);
  const { prev, current } = usePrevious(count);

  const onClick = () => {
    setCount((c) => c + 1);
  };
  console.log(prev, current);

  return (
    <div className="App">
      <button onClick={onClick}>increment</button>
      <div>{count}</div>
    </div>
  );
}

to define the usePrevious hook. In it, we just take the logic we previously had and put it in the hook function.

The only difference is that the hook takes the value parameter where value is the reactive value. And the hook function returns an object with the prev and current values that are derived from the same logic as before.

We use the usePrevious hook by passing the count state into it and take the prev and current properties from the object is returned.

Likewise, we can use the usePrevious hook to store the previous value of props by writing:

import { useEffect, useRef, useState } from "react";

const usePrevious = (value) => {
  const prevValueRef = useRef();
  useEffect(() => {
    prevValueRef.current = value;
  }, [value]);

  return { prev: prevValueRef.current, current: value };
};

const CountDisplay = ({ count }) => {
  const { prev, current } = usePrevious(count);
  console.log(prev, current);

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

export default function App() {
  const [count, setCount] = useState(0);

  const onClick = () => {
    setCount((c) => c + 1);
  };

  return (
    <div className="App">
      <button onClick={onClick}>increment</button>
      <CountDisplay count={count} />
    </div>
  );
}

We just call the usePrevious with the count prop in the CountDisplay component instead of in the App component.

Either way, we get the same result.

Conclusion

We can use refs to store the previous values of state and props since refs don’t trigger the re-rendering of components when their values change.


About the Author

John Au-Yeung

John Au-Yeung is a frontend developer with 6+ years of experience. He is an avid blogger (visit his site at https://thewebdev.info/) and the author of Vue.js 3 By Example.

Related Posts

Comments

Comments are disabled in preview mode.