Introduction to useRef in React
The useRef Hook lets a React component retain a value between renders without triggering a re-render when that value changes. It is also commonly used to access DOM elements directly, such as focusing an input, scrolling to a section, or measuring an element.
Unlike useState, changing a ref does not ask React to render the component again. This makes refs useful for values that need to persist but do not directly determine what appears on the screen.
What Is a Ref?
A ref is a plain JavaScript object with a mutable current property. React returns the same ref object across renders of the same mounted component.
import { useRef } from 'react';
function Example() {
const countRef = useRef(0);
function increment() {
countRef.current += 1;
console.log(countRef.current);
}
return (
<button onClick={increment}>
Increment ref
</button>
);
}
Each click updates the stored value, but the displayed interface does not automatically update because changing countRef.current does not trigger a render.
Basic Syntax
const ref = useRef(initialValue);
initialValueis the value assigned toref.currentwhen the ref object is created.refis the persistent object returned by React.ref.currentstores the current mutable value.- Updating
ref.currentdoes not trigger a component re-render.
For a refresher on state-driven rendering, see React State.
useRef vs useState
Both Hooks retain information between renders, but they serve different purposes. Choose state when changing a value should update the interface. Choose a ref when a value must persist without causing a render.
| Feature | useState | useRef |
|---|---|---|
| Persists between renders | Yes | Yes |
| Triggers a render when updated | Yes, when React processes a state update | No |
| Common purpose | Data displayed in the UI | DOM references and mutable values |
| Typical access | State value and setter | ref.current |
Accessing DOM Elements with useRef
One of the most common uses of useRef is referencing a DOM element rendered by React. Pass the ref to an element's ref prop, and React assigns the corresponding DOM node to ref.current when the element is mounted.
import { useRef } from 'react';
function SearchBox() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current?.focus();
}
return (
<div>
<input
ref={inputRef}
type="search"
placeholder="Search topics"
/>
<button onClick={focusInput}>
Focus search
</button>
</div>
);
}
export default SearchBox;
When the button is clicked, the input receives focus. The optional chaining operator (?.) prevents an error if the input is not currently available.
Scrolling to an Element
Refs can also be used to scroll to a particular element, such as a section in a long documentation page.
import { useRef } from 'react';
function DocumentationPage() {
const sectionRef = useRef(null);
function goToSection() {
sectionRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
return (
<div>
<button onClick={goToSection}>
Go to advanced topics
</button>
<div style={{ height: '700px' }}>
Introduction content
</div>
<section ref={sectionRef}>
<h2>Advanced topics</h2>
<p>Explore more advanced concepts here.</p>
</section>
</div>
);
}
Calling scrollIntoView() on the referenced element scrolls it into view. This is useful for navigation within articles, forms, and dashboards.
Storing Values Between Renders
A ref can hold information such as a timer identifier, an external library instance, or a previous value. React preserves the ref object for the lifetime of the mounted component.
import { useRef } from 'react';
function RenderCounter() {
const renderCount = useRef(0);
renderCount.current += 1;
return (
<p>
Render count: {renderCount.current}
</p>
);
}
This example demonstrates persistent mutable storage, but it is not a reliable pattern for displaying render counts. Updating a ref during rendering does not trigger another render, and rendering should remain free of side effects. Use an appropriate state-based approach when the count must be displayed and updated predictably.
Using useRef with Timers
Timer identifiers are another useful ref value. The identifier can be retained so the timer can be cleared when needed without introducing state updates.
import { useRef } from 'react';
function TimerControls() {
const timerRef = useRef(null);
function startTimer() {
if (timerRef.current !== null) return;
timerRef.current = setTimeout(() => {
console.log('Timer completed');
timerRef.current = null;
}, 2000);
}
function cancelTimer() {
if (timerRef.current !== null) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
}
return (
<div>
<button onClick={startTimer}>Start timer</button>
<button onClick={cancelTimer}>Cancel timer</button>
</div>
);
}
This example prevents multiple active timers and allows cancellation. For timers that must always be cleaned up when a component unmounts, add an Effect with a cleanup function. Learn more in React useEffect.
Tracking a Previous Value
A ref can preserve a value from an earlier render. An Effect can update that stored value after React commits the current render.
import { useEffect, useRef } from 'react';
function PriceDisplay({ price }) {
const previousPrice = useRef(price);
useEffect(() => {
previousPrice.current = price;
}, [price]);
return (
<div>
<p>Current price: {price}</p>
<p>Previous price: {previousPrice.current}</p>
</div>
);
}
The ref is updated after the render commits, so the displayed previous price represents the value stored before that update. This pattern is useful when comparing values, but avoid adding refs when ordinary state or derived data is sufficient.
Important Rules and Limitations
- Call Hooks at the top level: Call
useRefinside a function component or custom Hook, not inside loops, conditions, or nested functions. - Do not use refs for rendered data: Updating
ref.currentdoes not trigger a re-render. UseuseStateif the interface must react to a value changing. - Avoid reading or writing refs during rendering: Keep rendering pure. There are narrow exceptions for predictable initialization, but ordinary ref updates belong in event handlers or Effects.
- Handle missing DOM nodes: A ref may be
nullbefore an element mounts or after it unmounts. - Remember component lifetime: A ref persists while its component remains mounted. It is not a permanent store across page reloads or remounts.
- Do not overuse direct DOM manipulation: Prefer React props and state for normal UI updates. Use refs when an imperative operation is genuinely needed.
These rules follow the broader Rules of React Hooks and help prevent rendering bugs.
Common Mistakes
Expecting the UI to update after changing a ref
Assigning a new value to ref.current does not trigger a render. Use state when the updated value needs to appear in the interface.
Accessing a DOM node before it is mounted
A DOM ref is commonly null during the initial render. Access it in an event handler or an Effect after the element is mounted.
Using refs as a replacement for state
Refs are intended for mutable values that do not directly drive rendering. Storing visible application data in refs can leave the interface out of sync with the actual value.
Updating refs during rendering
Unconditional ref mutations during rendering can produce confusing behavior, especially when React performs additional renders. Prefer event handlers or Effects for updates.
When Should You Use useRef?
Use useRef when a component needs persistent mutable information or access to a DOM node without scheduling a re-render. Common examples include:
- Focusing an input or controlling media playback.
- Scrolling to a particular section or measuring an element.
- Storing timer identifiers or external object instances.
- Keeping track of a value between renders when the value itself does not determine the UI.
If the value should change what users see, prefer state. If you need to coordinate with external systems, consider whether useEffect is appropriate. For more complex state transitions, explore useReducer.
Key Takeaway
useRef provides a stable object whose current property can hold mutable data between renders. It is especially valuable for DOM access, timers, and imperative operations. Choose state for information that must update the interface, and use refs only when a re-render is not the intended result.