Referencing values with refs
Use useRef to remember values without re-rendering and to reach real DOM elements.
So far everything in your components has been driven by state: change state, React re-renders, the screen updates. But sometimes you need to remember something that has nothing to do with what is on screen, such as a timer ID. And sometimes you need to reach the real browser element, for example to put the cursor in a text box.
For both jobs React gives you an escape hatch: a ref. Escape hatches let you step outside React's normal data flow. Use them sparingly, but know they are there.
A value that doesn't trigger a render
Call useRef with an initial value. It returns an object with a single property, current:
import { useRef } from 'react';
const ref = useRef(0); // ref is { current: 0 }You can read and change ref.current whenever you like. React keeps the same object between renders, but changing it does not re-render your component.
The count goes up every click, yet the component never re-renders. That makes refs a poor fit for anything you display. If you want the number shown on the button, that is state.
useState | useRef | |
|---|---|---|
| Returns | [value, setValue] | { current: value } |
| Changing it re-renders | Yes | No |
| Read during render | Yes | No (except the first time you set it up) |
| Good for | Anything shown on screen | Timer IDs, DOM elements, values used only in event handlers |
A real use: a stopwatch
A stopwatch needs setInterval, which returns an ID. To stop it later you call clearInterval(id), so you must remember the ID. It is never shown on screen, so it belongs in a ref. The elapsed time is shown, so it belongs in state.
Refs to DOM elements
The most common use of refs is to reach a real DOM node. Pass your ref object to the ref attribute of a JSX tag. After React puts that element on the page, it sets ref.current to the DOM node, so you can call browser methods like focus() or scrollIntoView().
Here is the order of events. On the first render inputRef.current is null, because the input doesn't exist yet. Then React creates the <input> and sets inputRef.current to it. By the time the user clicks the button, the ref points at the input.
Passing a ref to your own component
What if the <input> lives inside a component you wrote, like <SearchInput />? In React 19 you pass ref like any other prop, and the child forwards it to the element it renders.
Deep diveWhat about forwardRef?
Older tutorials wrap components in forwardRef(...) to accept a ref. Before React 19, ref was not passed through as a normal prop, so that wrapper was required. Since React 19 you can read ref straight from props, as above, and forwardRef is no longer needed for function components. You will still see it in older code, so it is good to recognize.
You want to show how many times a button has been clicked, and update the number on screen. What should you use?
Focus the title after adding a book
When you add a book, the cursor should jump back to the title input so you can type the next one straight away. Use a ref.
Click the Add button with your mouse and notice the cursor stays on the button.
Show solution
import { useRef, useState } from 'react';
export default function AddBookForm() {
const [title, setTitle] = useState('');
const [titles, setTitles] = useState([]);
const inputRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
setTitles([...titles, title]);
setTitle('');
inputRef.current.focus();
}
return (
<>
<form onSubmit={handleSubmit}>
<input
ref={inputRef}
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Title"
/>{' '}
<button type="button" onClick={handleSubmit} disabled={!title}>
Add
</button>
</form>
<ul>
{titles.map((t, i) => (
<li key={i}>{t}</li>
))}
</ul>
</>
);
}Focusing an input is a browser action that React doesn't manage, so doing it from an event handler with a ref is exactly right.
Day 4 progress