Implement and understand usePrevious hook
How come useRef in React can be used to keep track of the previous value?
Introduction
You've probably seen the pattern where useRef
is used to keep track of the previous value of a state. But how does it always stay one time behind the useState
?
That's what we'll dive into today!
usePrevious hook
import { useRef, useEffect } from "react";
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
We have a custom hook called usePrevious
that takes a value and returns the previous value. Let's break it down:
We import
useRef
anduseEffect
from React.We define a function called
usePrevious
that takes avalue
as an argument.We create a
ref
usinguseRef()
.We set the
ref.current
to thevalue
inside theuseEffect
hook.We return
ref.current
.
So, how come ref.current
always stays one time behind the useState
?
To understand this, we have to understand how useRef
and useState
work.
useRef
useRef
returns a mutable object whose .current
property is initialized to the passed argument (initial value). Mutating the .current
property doesn't trigger a re-render. This is important to understand because it allows us to store mutable values that persist across renders.
useState
useState
returns an array with two values: the current state and a function to update the state. When you call the update function, React schedules a re-render with the new state.
Why useRef stays one time behind useState
During the very first render of the component, useEffect
hasn't had a chance to run yet. React's effect hooks run after the render phase. This is intentional so that they don't block the rendering process. This also applies to every re-render.
Now that we understand the different pieces, let's go over the flow:
During the initial render, the
useRef
hook is initialized with the providedinitialValue
, settingref.current
to that value.After the component renders and commits to the DOM, the
useEffect
runs and updatesref.current
with thenewValue
. Remember, this happens after the render phase. "Commit" means that React has applied the changes to the DOM.However, this update to
ref.current
does not trigger a re-render of the component.On the next render cycle (e.g. when state/props change), the
useRef
hook returns the same ref object it created during the initial render.This means that
ref.current
still holds thenewValue
from the previous render, not the current value.During that next render,
ref.current
will hold thenewValue
that was assigned in the previous render'suseEffect
.- This is the key point to understand. The
useEffect
hook updates theref.current
value, but that update doesn't trigger a re-render. That's why it doesn't update the UI that holds the ref value.
- This is the key point to understand. The
Conclusion
Understanding how the usePrevious
hook works requires understanding useRef
, useState
, useEffect
and the order in which they run. By understanding these concepts, you can build more complex hooks and components that leverage the power of React's hooks system.