DEV Community

Cover image for Create a modified useState hook to get previous value
Vivek Kumar
Vivek Kumar

Posted on

Create a modified useState hook to get previous value

In React Js, we use useState in almost every component. An example of useState is given below:

  const [count, setCount] = useState(0);
Enter fullscreen mode Exit fullscreen mode

In above example we have created a count state where we can use setCount to update the count. But, imagine a situation where you need to compare the previous count with current count ( i.e. count ).

In this tutorial, we will create a custom hook in which we will also get a third variable i.e. prevState

const [count, setCount, prevCount] = useStateModified(0);
Enter fullscreen mode Exit fullscreen mode

In above example, we have prevCount as well that we can use for our custom logic.

Step 1

Create a file in project directory with name useStateModified.jsx.

Step 2

Create a function with name useStateModified and export it as default export.

const useStateModified = () => {

};

export default useStateModified;

Enter fullscreen mode Exit fullscreen mode

Step 3

Create a state using useState, take initialState as parameter in our custom hook and return state and setState.

import { useState } from "react";

const useStateModified = (initalState) => {
  const [state, setState] = useState(initalState);
  return [state, setState];
};

export default useStateModified;
Enter fullscreen mode Exit fullscreen mode

Step 4

Till now it is nothing new and is equivalent to directly using useState in any component. To save the previous state we will use useRef hook. We will return prevState.current as third item of array.

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

const useStateModified = (initalState) => {
  const [state, setState] = useState(initalState);
  const prevState = useRef(initalState);

  return [state, setState, prevState.current];
};

export default useStateModified;
Enter fullscreen mode Exit fullscreen mode

Step 5

If you are aware of useRef, its value won't change until and unless we update its current property ( example - prevState.current = 'some value').
So now, we will write a useEffect hook that will listen for the changes in state value. Whenever there is a change in the state, we will also update prevState.

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

const useStateModified = (initalState) => {
  const [state, setState] = useState(initalState);
  const prevState = useRef(initalState);

  useEffect(() => {
    prevState.current = state;
  }, [state]);

  return [state, setState, prevState.current];
};

export default useStateModified;
Enter fullscreen mode Exit fullscreen mode

When there will be change in state, it will execute the line (return [state, setState, prevState.current]) before running useEffect statement.

Lets see how we can use it.

import { useEffect, useState } from "react";
import "./styles.css";
import useStateModified from "./useStateModified";

export default function App() {
  const [count, setCount, prevCount] = useStateModified(0);
  return (
    <div className="App">
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>
      <h2>Count: {count}</h2>
      <h2>Previous Count: {prevCount}</h2>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

useStateModified

This way you can create custom useState hook to achieve functionality where you need previous state.

Check demo here

Top comments (6)

Collapse
 
renatoargh profile image
Renato Gama • Edited

Thanks! It would be nice to get a TypeScript version. When I simply add T as the generic parameter the return type becomes T | Dispatch<SetStateAction<T>> which doesn't work seamlessly. Here is what I have done;

import { useEffect, useRef, useState } from 'react'

export function useStatePrevious<T>(initalState: T) {
  const [state, setState] = useState<T>(initalState)
  const previous = useRef<T>(initalState)

  useEffect(() => {
    previous.current = state
  }, [state])

  return [state, setState, previous.current]
}
Enter fullscreen mode Exit fullscreen mode

My real use case example;
Image description

Collapse
 
dsaga profile image
Dusan Petkovic

OK thanks, I get it now, the reference is actually always the same value as the state, its just because setting the ref doesn't cause a re-render so its showing the old value always, not actually holding the previous value.

Collapse
 
dsaga profile image
Dusan Petkovic

So if any part of the code depends on that ref to hold the previous value, it would produce invalid results...

Collapse
 
dsaga profile image
Dusan Petkovic

Would be nice to have a working demo somewhere of this? a bit confused on how this works

Collapse
 
vvkkumar06 profile image
Vivek Kumar

Added a demo link in bottom of the content

Collapse
 
vvkkumar06 profile image
Vivek Kumar

Sure will share online link