Hooks introduced in React 16.8, allow us to use stateful logic, lifecycle methods, and side-effects in Functional Components. React's official doc on Hooks is the best way to get you started with them.
useRef
is one such very helpful Hook that returns a mutable
ref object having current
property initialized with the value passed as the argument. This mutable object's value persists across multiple renders in the lifecycle of the component and can have various applications based on use-cases. In this blog, we'll understand different scenarios in which the useRef
Hook can be helpful with the aid of examples.
We'll look at following 3 applications of useRef
Hook with examples:
- Remembering previous value of a state variable
- Fixing closure issue while using intervals
- Referencing a DOM element
1. Remembering previous prop value:
Sometimes, we may want to know when the prop variable is updated and perform some operations based on its previous value. In class-based components, we have a lifecycle method componentDidUpdate
which helps us in this scenario. But in Hooks, we have no direct way to know previous prop values. Using the mutable object returned by the useRef
Hook, we can achieve this. Let's understand by creating a simple example:
Explanation:
Here, we have a textbox and a div
. The div
element will render the previous prop value prevVal
of the state variable name
passed to it. We have abstracted the logic to return the previous prop value in a Hook called usePrev
. Let's understand this line by line after Main
, called for rendering by ReactDOM.render()
:
-
Line 5: state variable
name
with initial value""
created. -
Line 6:
usePrev
Hook called withname
initial value passed as argument. Now, inside this Hook, -
-
Line 25:
useRef
Hook returns a mutable object which is then assigned toref
with itscurrent
value asundefined
. -
Line 30: Next, return value of
usePrev
is called, and it returnsref.current
which isundefined
.
Note:
useEffect
ofusePrev
Hook is not yet called. It will be called only after the component callingusePrev
is rendered. Thus,undefined
is returned toprevVal
in theMain
. -
Line 25:
-
Line 7: The control now returns to the
Main
, and it is rendered. -
Line 27-29: After
Main
renders,useEffect
ofusePrev
is called, which assigns value ofname
i.e.""
toref.current
.
Next time if we change the value of the textbox to say a
, the same flow will be triggered i.e. the prevValue
will be rendered as ""
which was the previous value of name
. After re-rendering of Main
completes, useEffect
of usePrev
will re-assign the value of name
i.e. ""
to ref.current
.
ref
value is updated only when its current
is explicitly assigned some value. This can also act as an instance variable of a class where some values need to be persisted/updated without causing overhead of re-rendering as in the case of state variables.
2. Working with setInterval:
Using intervals with Hooks becomes a bit tricky. We'll look at different approaches, problems we encounter and find out how useRef
helps in solving them.
Let's understand by creating a simple timer that counts from 99 to 1.
Here, we have a count
variable initialized to 99. We initialize the interval on the mount in useEffect
and clean it up on unmount.
After an interval of every 1000 ms, we are decrementing the value of count
. However, on running this, we observe that count
only decrements by 1 to 98 and remains as it is. This happens because useEffect
runs only on the mount and remembers the value of count
which is 99. The setInterval
closes over this value and runs setCount(count - 1)
and decrements count
to 98. Since effect runs only once the closure is not updated and setInterval
even though keeps running on every interval doesn't update the value and count
is stuck at 98.
We can fix this issue by removing the dependency array, thus calling useEffect
on every update of count
. But this will create a new interval on every update which is not desirable. You can check this by logging interval id in the callback.
The better approach would be as follows:
Explanation:
- Line 6: We create a mutable object with
useRef
and assign it tocallbackFn
- Line 8: We create a function
callback
that decrements thecount
until it is greater than 0. - Line 15 - 17: We assign this
callback
tocallbackFn
. ThisuseEffect
is called on every update ofcount
because a new instance ofcallback
is created on every update whose reference is assigned toref
variablecallbackFn
. - Line 19 - 24: We call
useEffect
on the mount to create an interval and callcallbackFn
on every interval.
Observe that we have created only one interval on the mount. The callback
's reference is assigned to ref callbackFn
on every re-render. This way we also fixed the closure problem we came across earlier, as a new instance of callback
is being assigned to callbackFn
on every re-render and callbackFn
having an updated instance is called on every interval of one second.
3. Reference to a DOM variable:
This is the most common usage of useRef
. Let's change the color of the counter to red when the mouse hovers on it and revert when the mouse leaves. We'll also pause the counter when the mouse enters and resume when it leaves. The code is as follows:
Explanation:
- Line 7: We create a ref variable
counterRef
. - Line 8: We create a state variable
isRunning
to toggle the running status of the counter. By default, it is set totrue
. - Line 9: We also create a state variable
delay
which will be used as interval delay insetInterval
. By default, it is set to1000
. - Line 41: We assign
counterRef
toh1
element which is rendering thecount
. - Line 42-45:
onMouseEnter
of theh1
, we update its color to red using its refcounterRef
. We also pause the counter by settingisRunning
tofalse
. - Line 46-49:
onMouseLeave
of theh1
, we update its color to black using its refcounterRef
and resume the counter by settingisRunning
totrue
. - Line 31-37: this
useEffect
is called on the update ofisRunning
. IfisRunning
is set to true, it setsdelay
tonull
. Else, it setsdelay
to1000
. - Line 22-29: The
useEffect
used for setting the interval is now updated with the dependency ofdelay
. Ifdelay
isnull
, that isonMouseEnter
of the counter, the interval creation block is skipped and counter is paused.onMouseLeave
of the counter, thedelay
is set to1000
, the interval is set and flow continues from where it stopped.
These are the practical usage of useRef
Hook in the React application. I hope this will help you to make the best use of useRef
Hook in your React application.
Thank you for reading!