React useRef Hook
useRef gives you a mutable object that persists for the full lifetime of a component. Changing it does not cause a re-render. This makes it useful in two distinct situations: accessing DOM elements directly, and storing values that need to survive re-renders but should not drive the UI.
How useRef works
useRef takes an initial value and returns a ref object with a single property: current.
const ref = useRef(initialValue);
// ref.current === initialValue
You can read and write ref.current at any time. Unlike state, updating it does not schedule a re-render.
Use case 1: Accessing DOM elements
Sometimes you need to work directly with a DOM element: to focus an input, measure its size, scroll to it, or pass it to a third-party library. React normally manages the DOM for you, but refs give you an escape hatch.
To attach a ref to a DOM element, pass it as the ref attribute:
import { useRef } from 'react';
export default function FocusInput() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} type="text" placeholder="Type here..." />
<button onClick={handleClick}>Focus the input</button>
</div>
);
}
After the component mounts, inputRef.current holds the actual <input> DOM node. You can call any native DOM method on it.
Focusing on mount
A common pattern is to focus an input as soon as the component appears. Combine useRef with useEffect:
import { useRef, useEffect } from 'react';
export default function SearchBox() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} type="search" placeholder="Search..." />;
}
Reading a value on submit
You can also read an input’s value when a form is submitted, without tracking every keystroke with useState. This is called an uncontrolled input.
import { useRef } from 'react';
export default function SimpleForm() {
const nameRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
console.log('Name:', nameRef.current.value);
}
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} type="text" placeholder="Your name" />
<button type="submit">Submit</button>
</form>
);
}
For most forms you should use controlled inputs with useState. Refs for form values work well for simple cases or when integrating with non-React code.
Use case 2: Persisting values without re-renders
A ref is also the right tool when you need a value to survive between renders but you do not want changes to that value to trigger a re-render.
Tracking the previous value of state
import { useState, useRef, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef(0);
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return (
<div>
<p>Current: {count}</p>
<p>Previous: {prevCount}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
The ref stores the previous render’s count value. Because updating a ref does not cause a re-render, this does not create a loop.
Storing a timer ID
When you start a setInterval inside a component, you need to store the ID somewhere so you can cancel it later. State would cause an unnecessary re-render. A ref is the right fit.
import { useState, useRef } from 'react';
export default function Stopwatch() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
function start() {
if (intervalRef.current !== null) return; // Already running
intervalRef.current = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
}
function stop() {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return (
<div>
<p>{seconds}s</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
intervalRef.current holds the interval ID. Reading or updating it has no effect on rendering.
How to update a ref
Assign directly to ref.current. There is no setter function.
ref.current = newValue;
This is intentional. Refs are a deliberate escape from React’s data-flow model. Use them sparingly.
Common mistakes
Reading ref.current during render
When a component first renders, a DOM ref’s current is null. The DOM element does not exist yet. Only access DOM refs inside event handlers or useEffect.
function MyComponent() {
const ref = useRef(null);
// Wrong: ref.current is null here during the first render
console.log(ref.current.value);
// Correct: read it inside an effect or event handler
useEffect(() => {
console.log(ref.current.value);
}, []);
return <input ref={ref} />;
}
Using a ref when you need state
If a value changing should update what the user sees, use useState, not useRef. Updating a ref does not re-render the component, so the UI will not reflect the new value.
// Wrong: counter never updates on screen
const countRef = useRef(0);
function handleClick() {
countRef.current += 1; // UI stays at 0
}
// Correct: counter updates on screen
const [count, setCount] = useState(0);
function handleClick() {
setCount(c => c + 1); // UI updates
}