React useRef Hook
Access DOM nodes directly and persist mutable values across renders without triggering re-renders with useRef.
TL;DR
- Hold persistent mutable values across render cycles using the
useRefhook. - Access underlying DOM elements directly by assigning references to the
refproperty. - Store background timer identifiers without triggering expensive component
re-renders.
DOM Node Access
Focus Input ElementCapture input element reference and trigger focus imperatively.
const inputRef = useRef<HTMLInputElement>(null);
const onFocus = () => inputRef.current?.focus();
return <InputBase ref={inputRef} />;Scroll to ViewScroll container smoothly into view using ref target.
const boxRef = useRef<HTMLElement>(null);
const scroll = () => {
boxRef.current?.scrollIntoView({
behavior: 'smooth',
});
};Measure DOM NodeRead bounding dimensions of rendered DOM elements.
const boxRef = useRef<HTMLDivElement>(null);
const getWidth = () => {
return boxRef.current?.offsetWidth;
};Storing Mutable Values
Timer ID StorageRetain interval ID across renders without causing UI updates.
const timerRef = useRef<number | null>(null);
const start = () => {
timerRef.current = window.setInterval(tick, 1000);
};
const stop = () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};Render CounterTrack the number of times a component renders.
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1;
});Previous Value TrackerStore prior prop or state values to detect changes.
const prevValRef = useRef(val);
useEffect(() => {
prevValRef.current = val;
}, [val]);Ref Forwarding in React 19
Standard Ref PropIn React 19, function components accept ref as prop.
export function CustomInput({
ref,
...props
}: { ref?: React.Ref<HTMLInputElement> }) {
return <InputBase ref={ref} {...props} />;
}Callback RefPass callback function to execute code on attach/detach.
const refCallback = (node: HTMLElement | null) => {
if (node) console.log('Mounted:', node);
};
return <CardView ref={refCallback} />;Imperative HandleExpose custom methods to parents using useImperativeHandle.
useImperativeHandle(ref, () => ({
reset: () => {
if (inputRef.current) inputRef.current.value = '';
},
}));Common Ref Antipatterns
Render-Time MutationMutating ref during render breaks purity and concurrent React.
// BAD: Mutating ref in render body
ref.current = computeValue();Overusing Refs for StateUsing ref instead of state leaves UI out of sync.
// BAD: UI will not update
ref.current = 'New Title';Missing Null GuardFailing to check if current is defined before invocation.
// SAFE: Optional chaining
ref.current?.focus();Tips
- Mutating the
ref.currentproperty never causes a component re-render, making it ideal for tracking background timers. - Always check that
ref.currentis not null before invoking native DOM methods like focus or scroll.
Warnings
- Do not read or write
ref.currentduring rendering because React expects component render functions to remain pure. - Avoid using
useRefas a substitute foruseStatewhen values directly determine what appears in JSX markup.
In Practice
Focus an input on mount and debounce search requests using a persistent ref timer.
- Create a ref for the input element and a ref for the debounce timer.
- Trigger input focus imperatively inside a mount effect.
- Clear and reset the timer ref on every keystroke event.
- Clean up active timers when the component unmounts.
export function LiveSearch({ onSearch }: SearchProps) {
const inputRef = useRef<HTMLInputElement>(null);
const timerRef = useRef<number | null>(null);
useEffect(() => {
inputRef.current?.focus();
return () => clearTimeout(timerRef.current ?? 0);
}, []);
const handleType = (text: string) => {
clearTimeout(timerRef.current ?? 0);
timerRef.current = window.setTimeout(() => {
onSearch(text);
}, 300);
};
return (
<InputBase ref={inputRef} onChange={handleType} />
);
}FAQ
Updating state via useState triggers a scheduled re-render of the component, whereas mutating ref.current stores data silently across renders without triggering any UI updates.
Yes. useRef is a general-purpose mutable box. You can store interval IDs, previous prop values, WebSocket instances, or cancellation tokens that persist across render passes.
React can render components multiple times before committing changes to the DOM. Writing or reading refs during render creates unpredictable side effects and breaks Concurrent Mode.