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
namewith initial value""created. -
Line 6:
usePrevHook called withnameinitial value passed as argument. Now, inside this Hook, -
-
Line 25:
useRefHook returns a mutable object which is then assigned torefwith itscurrentvalue asundefined. -
Line 30: Next, return value of
usePrevis called, and it returnsref.currentwhich isundefined.
Note:
useEffectofusePrevHook is not yet called. It will be called only after the component callingusePrevis rendered. Thus,undefinedis returned toprevValin theMain. -
Line 25:
-
Line 7: The control now returns to the
Main, and it is rendered. -
Line 27-29: After
Mainrenders,useEffectofusePrevis called, which assigns value ofnamei.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
useRefand assign it tocallbackFn - Line 8: We create a function
callbackthat decrements thecountuntil it is greater than 0. - Line 15 - 17: We assign this
callbacktocallbackFn. ThisuseEffectis called on every update ofcountbecause a new instance ofcallbackis created on every update whose reference is assigned torefvariablecallbackFn. - Line 19 - 24: We call
useEffecton the mount to create an interval and callcallbackFnon 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
isRunningto toggle the running status of the counter. By default, it is set totrue. - Line 9: We also create a state variable
delaywhich will be used as interval delay insetInterval. By default, it is set to1000. - Line 41: We assign
counterReftoh1element which is rendering thecount. - Line 42-45:
onMouseEnterof theh1, we update its color to red using its refcounterRef. We also pause the counter by settingisRunningtofalse. - Line 46-49:
onMouseLeaveof theh1, we update its color to black using its refcounterRefand resume the counter by settingisRunningtotrue. - Line 31-37: this
useEffectis called on the update ofisRunning. IfisRunningis set to true, it setsdelaytonull. Else, it setsdelayto1000. - Line 22-29: The
useEffectused for setting the interval is now updated with the dependency ofdelay. Ifdelayisnull, that isonMouseEnterof the counter, the interval creation block is skipped and counter is paused.onMouseLeaveof the counter, thedelayis 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!