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 with name initial value passed as argument. Now, inside this Hook,
    • Line 25: useRef Hook returns a mutable object which is then assigned to ref with its current value as undefined.
    • Line 30: Next, return value of usePrev is called, and it returns ref.current which is undefined.
    • Note: useEffect of usePrev Hook is not yet called. It will be called only after the component calling usePrev is rendered. Thus, undefined is returned to prevVal in the Main.
  • Line 7: The control now returns to the Main, and it is rendered.
  • Line 27-29: After Main renders, useEffect of usePrev is called, which assigns value of name i.e. "" to ref.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 to callbackFn
  • Line 8: We create a function callback that decrements the count until it is greater than 0.
  • Line 15 - 17: We assign this callback to callbackFn. This useEffect is called on every update of count because a new instance of callback is created on every update whose reference is assigned to ref variable callbackFn.
  • Line 19 - 24: We call useEffect on the mount to create an interval and call callbackFn 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 to true.
  • Line 9: We also create a state variable delay which will be used as interval delay in setInterval. By default, it is set to 1000.
  • Line 41: We assign counterRef to h1 element which is rendering the count.
  • Line 42-45: onMouseEnter of the h1, we update its color to red using its ref counterRef. We also pause the counter by setting isRunning to false.
  • Line 46-49: onMouseLeave of the h1, we update its color to black using its ref counterRef and resume the counter by setting isRunning to true.
  • Line 31-37: this useEffect is called on the update of isRunning. If isRunning is set to true, it sets delay to null. Else, it sets delay to 1000.
  • Line 22-29: The useEffect used for setting the interval is now updated with the dependency of delay. If delay is null, that is onMouseEnter of the counter, the interval creation block is skipped and counter is paused. onMouseLeave of the counter, the delay is set to 1000, 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!

References: