State is for data that the screen shows. But sometimes you need to remember something that the screen does not show, or reach a real element on the page. For these cases, React gives you useRef. It is small, but very useful once you understand it.
What is a ref?
A ref is a box that holds a value. You make one with useRef(initialValue). It returns an object with a single field, current, that holds the value.
import { useRef } from 'react';
const box = useRef(0);
console.log(box.current); // 0
box.current = 5; // change it
Two facts make refs special:
- React keeps the ref between draws, so the value is not lost, just like state.
- Changing
ref.currentdoes not make React draw the component again, unlike state.
So a ref is like a private notebook: you can write in it and read from it, but writing does not tell the screen to change.
Use 1: focus an input
The most common use is to reach a real element in the page. Give the element a ref prop. After the element is drawn, React puts it in ref.current.
function SearchBox() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} placeholder="Search..." />
<button onClick={handleClick}>Focus the search box</button>
</div>
);
}
Before the first draw, inputRef.current is null. After the page is drawn, it points to the real input element, so you can call browser methods such as .focus(). You can only use it in event handlers and effects, not while drawing.
A common case: focus the first field of a form when the page opens.
useEffect(() => {
inputRef.current.focus();
}, []);
Another case: when a form shows an error, move the focus to the first wrong field. That helps people who use keyboards or screen readers.
Use 2: scroll to an element
function Page() {
const bottomRef = useRef(null);
return (
<div>
<button onClick={() => bottomRef.current.scrollIntoView({ behavior: 'smooth' })}>
Go to the bottom
</button>
<div style={{ height: 1500 }}>Long content</div>
<p ref={bottomRef}>You reached the bottom!</p>
</div>
);
}
A chat window uses the same trick: after a new message is added, scroll the last message into view.
Use 3: keep a value that does not affect the screen
Some values must survive between draws, but do not change what is on the screen. A timer id is the classic example:
function Stopwatch() {
const [seconds, setSeconds] = useState(0);
const timerId = useRef(null);
function start() {
if (timerId.current) return; // already running
timerId.current = setInterval(() => setSeconds((s) => s + 1), 1000);
}
function stop() {
clearInterval(timerId.current);
timerId.current = null;
}
useEffect(() => stop, []); // stop when the component goes away
return (
<div>
<p>{seconds} seconds</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
The seconds value is state, because the screen shows it. The timerId is a ref, because the screen does not show it, and we do not want a redraw when we set it.
Use 4: remember the previous value
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function Price({ price }) {
const before = usePrevious(price);
return <p>Now {price}, before {before}</p>;
}
The hook returns the value from the last draw, because the effect updates the ref only after the draw is done. This small custom hook is a nice example of a ref storing a value quietly.
Ref or state: how to choose
- If the screen must change when the value changes, use state.
- If you only need to remember it, or hold a real element, use a ref.
- Never read or write
ref.currentwhile the component is drawing. Do it in event handlers or effects.
Refs and other components
A ref on your own component does not work the same way as on a plain element. In React 19 you can pass ref as a normal prop to your own components:
function FancyInput({ ref, ...props }) {
return <input ref={ref} className="fancy" {...props} />;
}
function Form() {
const ref = useRef(null);
return (
<>
<FancyInput ref={ref} />
<button onClick={() => ref.current.focus()}>Focus</button>
</>
);
}
In older versions of React (18 and before) you had to wrap the component in a function called forwardRef. You may see that in older code. It does the same job.
Uncontrolled inputs with refs
You can read an input's value with a ref instead of keeping it in state. This is called an uncontrolled input. It is shorter for simple cases, because you do not redraw on every key press:
function QuickForm() {
const emailRef = useRef(null);
function handleSubmit(event) {
event.preventDefault();
console.log('Email is', emailRef.current.value);
}
return (
<form onSubmit={handleSubmit}>
<input ref={emailRef} type="email" defaultValue="" />
<button>Send</button>
</form>
);
}
Notice defaultValue instead of value: it sets only the starting text, and the browser keeps control after that.
Common mistakes
- Reading
ref.currentwhen it is stillnull, for example during the first draw. Use it in handlers and effects. - Using a ref for data that the screen shows, so the screen does not update.
- Forgetting
.current. The ref is an object. The value is inref.current. - Writing to
ref.currentwhile drawing. It makes the component unpredictable. - Using refs to change the page in ways React should handle. Prefer state for anything that changes what is drawn.
A worked example: a chat box
Here is a small chat that uses a ref twice: to scroll to the newest message, and to put the cursor back in the input after sending.
function Chat() {
const [messages, setMessages] = useState([]);
const [text, setText] = useState('');
const endRef = useRef(null);
const inputRef = useRef(null);
useEffect(() => {
endRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
function handleSubmit(event) {
event.preventDefault();
if (!text.trim()) return;
setMessages([...messages, { id: crypto.randomUUID(), text: text.trim() }]);
setText('');
inputRef.current.focus();
}
return (
<div>
<ul>
{messages.map((m) => <li key={m.id}>{m.text}</li>)}
</ul>
<div ref={endRef} />
<form onSubmit={handleSubmit}>
<input ref={inputRef} value={text} onChange={(e) => setText(e.target.value)} />
<button>Send</button>
</form>
</div>
);
}
The messages and the text are state, because the screen shows them. The two refs only point at real elements, so we can call browser methods on them. After each new message the effect scrolls an empty div at the bottom into view. After sending, the input gets focus again, so the person can keep typing without clicking. This is a good picture of the right split: state for what is shown, refs for reaching into the page.
Questions beginners often ask
Can I have many refs in one component? Yes. Make one useRef for each thing you need. For a list of items you can store the elements in an object inside one ref, or use a ref callback.
Is it wrong to change the page directly with a ref? For small things like focus and scrolling it is fine. Avoid using refs to change what is drawn, such as text or classes, because React will not know and may overwrite your changes. Use state for that.
Does changing a ref cause a render? No, and that is the point. If you need the screen to update, use state instead.
Your task
- Make a form that focuses the name field when the page opens.
- Make a button that scrolls smoothly to a section at the bottom of the page.
- Make a stopwatch with Start, Stop and Reset buttons using a ref for the timer id.
- Show the previous value of a counter using
usePrevious.
How to check it worked
- The cursor is in the name field when the page opens.
- The stopwatch stops when you press Stop and does not run twice when you press Start twice.
- No timers keep running after the component is removed.
Summary
useRef(initial)gives an object withcurrent. React keeps it between draws, and changing it does not cause a redraw.- Put a ref on an element to get the real DOM node, for example to focus it or scroll it into view.
- Use refs for timer ids, previous values and other things the screen does not show.
- If the screen shows it, use state, not a ref.