Ohans Emmanuel Author of Understanding Redux. I love God. I love GF a little too much 💕🤣 Read The Redux.js Books.

Accessing previous props or state with React Hooks

5 min read 1565

Accessing Props React Hooks

Editor’s note: This post was last updated 28 January 2022 to include state management with third-party libraries.

Accessing the previous props or state from within a functional component is one of those deceptively simple problems you’ll likely face as you work with React Hooks. While there’s currently no React Hook that does this out of the box, you can manually retrieve either the previous state or props from within a functional component by leveraging the useRef, useState, usePrevious, and useEffect Hooks in React. In this article, we’ll learn how.

React Hook Previous Diagram

Table of contents

Example: Getting the previous props or state

If you look in the official React documentation, you’ll see the following example, where Counter represents a simple counter component:

function Counter() {
  const [count, setCount] = useState(0);
//the useRef Hook allows you to persist data between renders
  const prevCountRef = useRef();
  useEffect(() => {
    //assign the ref's current value to the count Hook
    prevCountRef.current = count;
  }, [count]); //run this code when the value of count changes
  return (
    <h1>
      Now: {count}, before: {prevCountRef.current}
      {/*Increment  */}
      <button onClick={() => setCount((count) => count + 1)}>Increment</button>
    </h1>
  );
}

The code above generates the following output:

React Sample Get Previous Props

Custom Hook with the usePrevious Hook

But, if you’re looking for an even quicker solution, you can build the following custom Hook:

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value; //assign the value of ref to the argument
  },[value]); //this code will run when the value of 'value' changes
  return ref.current; //in the end, return the current ref value.
}
export default usePrevious;

To use the custom Hook within your app, write the following code:

function Counter() {
  const [count, setCount] = useState(0);
  // 👇 look here
  const prevCount = usePrevious(count)

  return <h1> Now: {count}, before: {prevCount} </h1>;
}

Use Custom React Hook App

Although this example seems straightforward, can you really explain how the usePrevious Hook works? Let’s explore in clear terms what’s happening within the custom Hook and how the previous value is retained.

We made a custom demo for .
No really. Click here to check it out.

useRef in depth: An overlooked React Hook

I work with Hooks everyday, both for personal projects and professional workplace projects. In my experience, I’ve found that very few people really understand and take advantage of the useRef Hook.

Apart from being great at handling DOM refs, the useRef Hook is a perfect substitute for implementing instance-like variables within functional components. Furthermore, it remembers data between UI renders, allowing developers to access previous, overwritten values.

Consider the following example with class components:

// class component 
class Count extends React.Component {

   constructor() {
     this.specialVariable = "SPECIAL_VARIABLE"
   }

  render() {
    return null
  }
}

Every instantiated copy of the Count class will have its own specialVariable instance variable. The useRef Hook can simulate this behavior with an even more interesting feature:

// functional component 

function Count() {
  const specialVariable = useRef("SPECIAL_VARAIBLE"); //store initial value into the specialVariable Hook. 
  return null
}

The useRef Hook takes in an initial value to be stored, i.e., useRef("INITIAL_VALUE"), and it returns an object with a current property {current: "INITIAL_VALUE"}.

Whatever value was initially passed into the useRef Hook is saved to the current property of the ref object:

function Count() {
  const specialVariable = useRef("SPECIAL_VARAIBLE");
  // specialVariable resolves to {current: "SPECIAL_VARIABLE"}
  console.log(specialVariable); //for debugging purposes, log out its value
  return null;
}

Value Passed Useref Hook Saved

Unlike a normal variable, the specialVariable ref object is not recomputed when the Count component is re-rendered. With the useRef Hook, the value saved in the ref object is kept the same across re-renders. The value is neither lost nor recomputed; it remains the same.

Retrieving previous props and state with useRef

It’s worth mentioning that the only way to update the ref object is to directly set the value of the current property, i.e., specialVariable.current = "NEW_SPECIAL_VARIABLE. Why is this explanation important?

Well, to understand what’s going on, let’s walk through the execution of the aforementioned solution to retrieving previous props and state step-by-step:

// custom hook for getting previous value 
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  },[value]);
  return ref.current;
}

// the App where the hook is used 
function Counter() {
  const [count, setCount] = useState(0);
  // 👇 look here
  const prevCount = usePrevious(count)

  return <h1>Now: {count}, before: {prevCount}</h1>;
}

// How the App is rendered 
<Counter />

When the rendering process for the Counter app begins, first, the useState Hook is invoked, and the variables count and setCount are set. Note that count is now 0:

useState Hook Invoked

Afterward, the next line will be executed. The usePrevious Hook is invoked with the current value of the count state variable, 0:

Useprevious Hook Invoked

Upon invoking the usePrevious Hook, React creates a new useRef instance:

React New Useref Instance

The initial value of this Hook is undefined. The console returns {current: undefined} because the current data doesn’t exist.

This next step is where most people slip up. React doesn’t execute the useEffect call, instead, the current value of the custom Hook is returned:

Current Value Custom Hook Returned

The useEffect Hook is invoked only after the component from which it is called has been rendered. Essentially, the return value of the component must be executed first.

Next, the execution within the component resumes. This time, the prevCount variable holds the value undefined:

Prevcount Variable Value Undefined

The return value of the component is evaluated:

Component Return Value Evaluated

This process returns <h1>Now: {count}, before: {prevCount}</h1> to the screen, where count and prevCount are 0 and undefined, respectively.

To avoid blocking the browser from painting the DOM changes, the useEffect call within the usePrevious Hook is now invoked asynchronously. useEffect is invoked after the functional component renders:

Useeffect Invoked Functional Component Render

Within the effect function, we have the following:

useEffect(() => {
    ref.current = value;
},[value]);

The line within the useEffect function updates the current property of the ref object to value.value now represents what the custom Hook was initially called with.

In this case, the value is 0. In this current flow, remember usePrevious has only been called once with the initial value of 0.

Useprevious Called Once Value

Now, the ref holds the value 0. When the count state variable within the app is updated from 0 to 1, or to a new count, the app will run this piece of code again.

The usePrevious Hook is invoked with the new state value 1. Then, the return statement is evaluated, returning ref.current, which would be 0, not 1, since the ref object isn’t updated yet.

Here, ref.current is the previous value stored before the useEffect was triggered, or 0. The return statement of the component is equally evaluated with the previous value successfully returned.

Only after the render is the useEffect call within the usePrevious Hook updated with the new value, 1. This cycle continues, and in this way, you’ll always get the previous value passed into the custom Hook, usePrevious.

Why this works

To understand why we can access previous props or state in this manner, you must remember the following:

  1. The ref object will always return the same value held in ref.current, except when it is explicitly updated
  2. useEffect is only called after the component is rendered with the previous value. Only after the render is done is the ref object updated within useEffect

By taking advantage of these two facts, you can easily replicate this functionality on your own.

Using Zustand

If you want to use a third-party library for easy state management, I recommend the Zustand API. Zustand allows you to create subscribers, which are functions that let you get the previous and current value of your state variables.

First, create a store and its subscriber as follows:

import create from "zustand";
const useStore = create((set) => ({
  dollars: 0,
  increaseIncome: () => set((state) => ({ dollars: state.dollars + 1 }))
}));
//create a subscriber
const unsubscribe = useStore.subscribe((newValue, oldValue) => {
  console.log(
    "new Value " + newValue.dollars + ", old Value:" + oldValue.dollars
  );
});

First, we declared a global variable called dollars. Its initial value will be 0. Then, we created a function called increaseIncome. If the user executes this method, the program will increment the dollars state.

We then coded a subscriber function that will log out the current value and old value of the dollars variable. To use this store in action, write the following code:

export default function App() {
  //extract our state and functions
  const dollars = useStore((state) => state.dollars);
  const increaseIncome = useStore((state) => state.increaseIncome);
  return (
    <div>
      <h1> Current: ${dollars} </h1>
      {/*When clicked, execute the 'increaseIncome' function */}
      <button onClick={() => increaseIncome()}>Increment </button>
    </div>
  );

With the code above, we procured the dollars variable and the increaseIncome function from our custom store:

Zustand Library State Management

Conclusion

In this article, we learned how to procure previous values from the useRef, useState, usePrevious, and useEffect Hooks in React.

The ref object returned from invoking useRef remains the same across re-renders of a functional component, which is a powerful feature to embrace. It does this without you having to pass in any array dependency, like in useMemo or useCallback.

When you combine the ability to use useRef as an instance variable with the fact that the useEffect Hook is always triggered after the return statement of the parent component is evaluated, you have an invaluable weapon at your fingertips.

Thanks for reading, and happy coding!

Ohans Emmanuel Author of Understanding Redux. I love God. I love GF a little too much 💕🤣 Read The Redux.js Books.

16 Replies to “Accessing previous props or state with React Hooks”

  1. You can’t use useEffect hook for this case because the effect might run after several renders asynchronously.
    For this case you should use useLayoutEffect hook which runs immediately after each render and in addition this logic doesn’t block painting then it’s fine to use it.

  2. I don’t want to come off rude, but your explanation is wrong.

    useEffect, yes will be run async, but it’ll run after the render NOT “several renders”.

    useLayoutEffect is pointless here since we aren’t reaching to the DOM for anything. Like you said, no painting. We really don’t get any added advantage using useLayoutEffect. Also, we intentionally want to run the side effect AFTER render so we save the previous value, hence using useEffect. Read some more here: https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state

  3. In usePrevious, shouldn’t the useEffect have a dependency on value? Otherwise if the component is re-rendered due to a different state change, in the next render, previousValue will equal value, right? Or am I missing something?

    1. Hi Aziz. This is correct, but also how the hook would work. It should return previous value related to previous renders. Regardless of how the render came about.

      Yes, you can put “value” in the dependency array – that’s a different use case but equally valid.

  4. Hey there!

    You’re explanation is really great such as this article. I like it very much, it helped me to break down things. However there might be a slight mistake in the article?

    “(4) The useEffect call is NOT invoked. Instead, the return value of the custom Hook is invoked.”

    Well I think it’s actually invoked… so JavaScript doesn’t magically skip this part, it executes top to bottom right? So the useEffect() actually invoked, but it’s parameter is an anonymus function: () => { ref.current = value }. And that inner function only gets invoked after the render part, because useEffect “smart” enough to know that right?

    Is that right to assume this way?

  5. I did the same thing and the previous prop and current props is the same. it shows the same value.
    i use reduxstore to manage the state in the component.

  6. No it is actually skipped and ran after the component is rendered, i.e. after the return statement.

    Try this:
    1. Put a console.log(“inside useEffect”) statement inside the useEffect()
    2. A hello() function with console.log(“inside hello”) inside the return statement in the component

    In the console, “inside hello” will appear before “inside useEffect”.

    Try and run the code snippet below. It is not inside a separate hook but the logic is same.

    import React, { useEffect } from “react”;

    export default function App() {
    const hello = () => {
    console.log(“inside hello”);
    };

    useEffect(() => {
    console.log(“inside useEffect”);
    });

    return {hello()};
    }

    Hope I answered your question.

  7. Excellent explanation of extremely opaque topic. It’s basically how I feel about hooks. They’re simple to use unless you want to understand what you’re doing. I like typing less but I miss the clarity of the old ways.

  8. usePrevious hook storing previous value if its object. its getting updated to new value on every rerender. I have used JSON.stringify to convert object to string and stored in usePrevious. in this way its working

  9. 1st example (which doesn’t work), can we assign a ref to a value ?

    you comment
    //assign the ref’s current value to the count Hook

    Where in fact you assign the state (not hook) value to a ref which doesnt make sense unless its a ref type not a value.

    in fact you are doing in useEffect like if it was in a react element initializer attribut : ref={refname}

Leave a Reply