What is useRef()?
The useRef() hook in React provides a way to create a mutable reference object whose .current property can hold any mutable value. This value persists across component re-renders, unlike regular variables which are re-initialized on each render.
When you call useRef(), it returns an object that looks like this: { current: initialValue }.
Why use useRef()?
There are two primary use cases for useRef():
- Accessing and interacting with the DOM elements directly: This is perhaps its most common use. React generally encourages declarative rendering, but sometimes you need to imperatively interact with the DOM (e.g., focusing an input, playing media, measuring an element's size).
- Storing a mutable value that does not cause a re-render when updated: Unlike
useState(), updating the.currentproperty of a ref does not trigger a re-render of the component. This makes it ideal for storing values that need to persist but don't directly affect the UI (e.g., a timer ID, a previous value of a prop/state).
How it Works (and Differs from useState)
useRef()returns a mutable ref object.- The ref object will persist for the full lifetime of the component.
- Changing the
.currentproperty of a ref does not trigger a re-render. This is the key difference fromuseState(), where updating state does trigger a re-render. - You might think of
useRefas providing an "instance variable" for your functional components.
Example 1: Accessing a DOM Element
Let's say you have an input field and you want to programmatically focus it when the component mounts or when a button is clicked.
import React, { useRef, useEffect } from 'react';
function MyInputFocus() {
const inputRef = useRef(null); // Initialize with null, as the ref will be attached to a DOM element
useEffect(() => {
// `current` property is where the DOM node is stored
if (inputRef.current) {
inputRef.current.focus();
}
}, []); // Empty dependency array means this runs once after the initial render
const handleClick = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<input type="text" ref={inputRef} placeholder="Focus me!" />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
In this example:
- We declare
inputRef = useRef(null). - We attach this ref to the
<input>element using therefattribute:<input ref={inputRef} />. React will setinputRef.currentto the actual DOM node of the input element once it's rendered. - Inside
useEffect, we accessinputRef.currentto call thefocus()method on the input element. - The button's click handler also demonstrates how you can imperatively focus the input whenever needed.
Example 2: Storing a Mutable Value Without Causing Re-renders
Consider a scenario where you want to keep track of a count, but updating this count should not trigger a re-render of the component. This is useful for things like keeping track of how many times a certain function was called, or storing a timer ID.
import React, { useRef, useState } from 'react';
function CounterWithoutReRender() {
const countRef = useRef(0); // Initialize the ref with 0
const [renderCount, setRenderCount] = useState(0); // State to show component re-renders
const incrementCount = () => {
countRef.current = countRef.current + 1; // Update the ref's current value
console.log('Ref Count:', countRef.current);
// Notice: This update does NOT cause the component to re-render!
};
const forceReRender = () => {
setRenderCount(prev => prev + 1); // This *will* cause a re-render
};
return (
<div>
<p>Ref Count: {countRef.current} (This value updates internally, but not on screen until forced re-render)</p>
<p>Render Count (via useState): {renderCount}</p>
<button onClick={incrementCount}>Increment Ref Count</button>
<button onClick={forceReRender}>Force Re-render (to see updated Ref Count)</button>
<p>
<small>Check the console to see ref count updating without re-render.</small>
</p>
</div>
);
}
In this example:
countRef.currentis incremented, but the component does not re-render. The UI displays the value ofcountRef.currentfrom the last render.- You need to click "Force Re-render" (which updates a separate
useStatevariable) to see the updatedcountRef.currentreflected in the UI. - This illustrates that
useRefis great for values that need to be mutable and persistent but don't directly drive UI changes.
When to Choose useRef vs. useState
useRef():- For storing values that you want to persist across renders but whose changes should not trigger a re-render.
- For direct interaction with DOM nodes.
- Like instance variables in class components.
useState():- For managing component-specific data that does affect the UI and needs to trigger re-renders when updated.
- The primary way to manage reactive state in functional components.
Conclusion
useRef() is a vital hook in your React toolkit, especially when you need to bridge the gap between React's declarative nature and imperative DOM manipulations, or when you simply need to store a mutable value that persists across renders without triggering unnecessary re-renders. Understanding its unique properties will help you write more efficient and robust React applications.