useRef
Sometimes you need direct access to an element on the page: put the cursor in a field, scroll a list, measure a size. That is what the useRef hook is for. It creates a ref — an object with a single property, current, that is kept between renders. Changing current does not trigger a re-render; that is the main difference from state.
A ref to a DOM element
Let us put the cursor into the add-task field with a button. In src/components/AddTodoForm.jsx:
// src/components/AddTodoForm.jsx
import { useRef } from 'react';
export default function AddTodoForm() {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} placeholder="New task" />
<button onClick={handleFocus}>Enter a task</button>
</>
);
}The ref attribute tells React: "put this DOM element into inputRef.current". After clicking the button, the cursor is in the field.
Focus after submitting
It is handy to return the cursor to the field after adding a task, so the next one can be typed right away:
// src/components/AddTodoForm.jsx
function handleSubmit(event) {
event.preventDefault();
onAdd(text);
setText('');
inputRef.current.focus();
}Add a task — the field clears and the cursor stays in it.
A value without re-rendering
A ref also suits data that is not shown on the page, such as a timer id. A stopwatch — src/components/Stopwatch.jsx:
// src/components/Stopwatch.jsx
import { useRef, useState } from 'react';
export default function Stopwatch() {
const [seconds, setSeconds] = useState(0);
const timerRef = useRef(null);
function start() {
timerRef.current = setInterval(() => setSeconds((value) => value + 1), 1000);
}
function stop() {
clearInterval(timerRef.current);
}
return (
<>
<p>Elapsed: {seconds} s</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</>
);
}The timer id is needed only by the "Stop" button and is never shown, so it lives in a ref rather than in state.
A ref in your own component
In React 19 ref can be passed to your own component as an ordinary prop and attached to an element inside:
// src/components/TextInput.jsx
export default function TextInput({ ref, ...props }) {
return <input ref={ref} className="text-input" {...props} />;
}Now <TextInput ref={inputRef} /> works just like input. Older versions required the forwardRef function for this.
useRef or useState
useRef | useState | |
|---|---|---|
| A change re-renders the component | No | Yes |
| How to change it | ref.current = value | Through the setter |
| What it is for | DOM elements, timers, internal values | Everything visible on screen |
Common mistake
During rendering ref.current is still empty: React fills it only once the element is on the page. So read it in event handlers and effects, not in the component body:
// Wrong: on the first render inputRef.current is null.
const inputRef = useRef(null);
inputRef.current.focus();To focus right after the element appears, call focus() inside useEffect or use the autoFocus attribute.