useRef Hook
Overview
While useState is perfect for data that should trigger a visual re-render when it changes (like a counter or a toggled menu), sometimes you need to store data that changes without triggering a re-render. This is where `useRef` comes in.
useRef is essentially a 'secret pocket' for your component. It returns a mutable object with a single property: .current. You can store any value in this property (numbers, strings, objects). When you update ref.current, React completely ignores the change and does NOT re-render the component.
useRef has two primary use cases:
1. Holding mutable values that shouldn't affect the UI (like storing a setInterval ID so you can clear it later, or tracking how many times a component has rendered).
2. Direct DOM Access: React is declarative, meaning you usually let React handle the DOM. But sometimes you must access a raw HTML element directly (e.g., to focus an <input>, or draw on a <canvas>). You can pass a ref to the ref attribute of an HTML tag, and React will assign that raw DOM node to ref.current.
Syntax
import { useRef } from 'react';
function CustomInput() {
// 1. Create a ref initialized to null
const inputRef = useRef(null);
const focusInput = () => {
// 3. Access the raw HTML DOM node and call native methods on it
inputRef.current.focus();
inputRef.current.style.backgroundColor = 'yellow';
};
return (
<div>
{/* 2. Attach the ref to the element using the special 'ref' prop */}
<input ref={inputRef} type="text" placeholder="Type here..." />
<button onClick={focusInput}>Focus the Input</button>
</div>
);
}function Stopwatch() {
const [time, setTime] = useState(0);
// We need to store the interval ID so we can stop it later.
// If we used useState, storing the ID would cause a useless re-render.
const timerId = useRef(null);
const start = () => {
// Mutating .current does NOT trigger a re-render
timerId.current = setInterval(() => setTime(t => t + 1), 1000);
};
const stop = () => {
clearInterval(timerId.current);
};
return ( /* ... */ );
}Common Pitfalls
- Displaying ref values in JSX: Do not write
<h1>{myRef.current}</h1>. Because updating a ref does not trigger a re-render, if the ref value changes from 1 to 2, the screen will still display 1 until something else (like a state change) forces the component to re-render. Only useuseStatefor data displayed on the screen.
Interview Questions
useState causes the component to re-render when the value changes. It is meant for data that affects the visual UI. useRef does NOT cause a re-render when its value changes. It is used to store mutable data behind the scenes or to hold references to direct HTML DOM nodes.
{ current: value } instead of just the value directly?By returning a consistent object reference, React ensures that the variable points to the exact same location in memory across all re-renders. If you mutate the .current property, the memory address of the object itself doesn't change, allowing the value to persist across render cycles without triggering React's diffing engine.
Real-World Example
Tracking Previous State: This is a classic senior-level pattern. Because refs are synchronous and persist across renders, you can use them to 'remember' what a prop or state variable was during the previous render cycle, allowing you to calculate trends or diffs.
function PriceTracker({ currentPrice }) {
// We want to know if the price went up or down compared to yesterday.
const prevPriceRef = useRef(currentPrice);
useEffect(() => {
// After every render, we update the ref to hold the current price.
// Because this happens AFTER the render, during the next render,
// the ref will still hold the OLD price!
prevPriceRef.current = currentPrice;
}, [currentPrice]);
const prevPrice = prevPriceRef.current;
const trend = currentPrice > prevPrice ? '🟢 Up' : '🔴 Down';
return (
<div>
<h2>Current: ${currentPrice}</h2>
<p>Trend: {trend}</p>
</div>
);
}Check Your Knowledge
Test your understanding of useRef Hook with these quick questions.