React wants you to describe the UI with state and props, and it handles the page for you. But sometimes you need to step outside that: focus a field, scroll to an element, measure a box, or keep a value that must not trigger a render. The tool for this is the ref.
useRef
useRef returns a plain object with a single property, current, that React keeps the same across renders:
import { useRef, useState } from "react";
function App() {
const clicks = useRef(0); // { current: 0 }
const [shown, setShown] = useState(0);
function handleClick() {
clicks.current = clicks.current + 1;
console.log("ref value:", clicks.current);
}
return (
<div>
<button onClick={handleClick}>Click (ref changes, no render)</button>
<p>Last state value shown: {shown}</p>
<p>Ref value read during render: {clicks.current}</p>
<button onClick={() => setShown(clicks.current)}>Copy ref into state</button>
</div>
);
}
export default App;Click (ref changes, no render)
Last state value shown: 0
Ref value read during render: 0
Copy ref into state
CONSOLE
ref value: 1
ref value: 2
ref value: 3After three clicks the Console says the ref reached 3, but the "read during render" line still shows 0, because changing a ref does not cause a render. The text would update only when something else triggers a render; press the second button to copy the ref into state and everything refreshes. That is the difference in one picture:
| State | Ref | |
|---|---|---|
| Survives renders | yes | yes |
| Changing it re-renders | yes | no |
| Read/write during rendering | read | avoid both (except setting up the initial value) |
| Use for | anything shown on screen | things outside the screen's output |
Never read or write ref.current during rendering, apart from setting an initial value; a component must stay pure. Use it in event handlers and effects.
Focusing an element: DOM refs
The most common use is a reference to a DOM element. Create a ref, pass it in the ref prop of an element, and React sets current to the real element once it is on the page:
import { useRef } from "react";
function App() {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current.focus();
inputRef.current.value = "focused by code";
}
return (
<div>
<input ref={inputRef} placeholder="Click the button" />
<button onClick={handleFocus}>Focus the input</button>
</div>
);
}
export default App;Focus the inputinputRef.current is the actual <input> DOM node, so you can call any DOM method on it: focus(), scrollIntoView(), play() on a video, getBoundingClientRect() to measure. A ref starts as null and is filled in after the element is added to the page, so use it in handlers or effects, never in the component body on the first render.
Typical uses:
- focus a field when a form opens or when a validation error appears,
- scroll to a message or to the bottom of a chat,
- play, pause or seek media,
- measure the size or position of an element,
- integrate with a non-React library that needs a DOM node.
Do not use refs to change what the page shows (text, classes, styles) that state can control. If you edit the DOM yourself, React will be unaware and may overwrite or conflict with your changes.
Refs for values that are not displayed
A ref is also a good place for a value that you need to remember between renders but that does not affect the output. A typical example is a timer id, so that you can cancel it later:
import { useRef, useState } from "react";
function App() {
const [seconds, setSeconds] = useState(0);
const timerId = useRef(null);
function start() {
if (timerId.current !== null) return; // already running
timerId.current = setInterval(() => setSeconds(s => s + 1), 500);
}
function stop() {
clearInterval(timerId.current);
timerId.current = null;
}
return (
<div>
<p>Ticks: {seconds}</p>
<button className="start" onClick={start}>Start</button>
<button className="stop" onClick={stop}>Stop</button>
</div>
);
}
export default App;The interval id is not part of what the user sees, and it must not change when the component re-renders (a normal let timerId would be recreated every render and lose the id). A ref keeps it. (A timer that should start automatically and stop when the component disappears is better handled in an effect, which the next lessons cover.)
Another use is remembering the previous value of something, or holding a flag such as "has the first render happened".
Refs and the DOM in lists
A ref can hold anything, including a map of elements. A ref callback lets you collect one per item:
import { useRef } from "react";
function App() {
const itemRefs = useRef(new Map());
const items = ["One", "Two", "Three"];
function findLast() {
const node = itemRefs.current.get("Three");
console.log("found node for", node.textContent);
}
return (
<div>
<button onClick={findLast}>Find the last item</button>
<ul>
{items.map(item => (
<li
key={item}
ref={node => {
itemRefs.current.set(item, node);
return () => itemRefs.current.delete(item); // React 19: cleanup when removed
}}
>
{item}
</li>
))}
</ul>
</div>
);
}
export default App;Find the last item
One
Two
Three
CONSOLE
found node for ThreeIn React 19 a callback ref may return a cleanup function, called when the element is removed; this is neat for registering and unregistering elements. (In earlier versions, the callback is called with null on removal instead.)
Uncontrolled inputs
In the Controlled Inputs lesson, state held the text of each field. An uncontrolled input is the opposite: the DOM keeps the text, and you read it when you need it. Give it a starting value with defaultValue (or defaultChecked), and read the current value through a ref:
import { useRef } from "react";
function App() {
const nameRef = useRef(null);
function handleSubmit(event) {
event.preventDefault();
console.log("Hello,", nameRef.current.value);
}
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} defaultValue="" placeholder="Name" />
<button>Greet</button>
</form>
);
}
export default App;Greet
CONSOLE
Hello, ReactThis is less code for simple forms, and it re-renders nothing as you type. The cost is that React does not know the value while the user types, so you cannot easily validate on each keystroke, show a counter, or sync two fields. Choose:
- Controlled when the value drives other things on screen, or you need live validation or formatting.
- Uncontrolled (or
FormData) for simple "collect on submit" forms and when integrating with plain-HTML-style code.
One input type is always uncontrolled in React: <input type="file" />. Its value can only be set by the user, so read ref.current.files or use FormData.
ref as a prop in React 19
When you want a parent to reach a DOM element that lives inside your own component (for instance, to focus the input inside a TextField), the parent passes a ref prop, and your component hands it to the element. In React 19, function components receive ref like any other prop:
import { useRef } from "react";
function TextField({ label, ref }) {
return (
<label>
{label}: <input ref={ref} />
</label>
);
}
function App() {
const fieldRef = useRef(null);
return (
<div>
<TextField label="Search" ref={fieldRef} />
<button onClick={() => { fieldRef.current.focus(); console.log("focused:", fieldRef.current.tagName); }}>
Focus the search field
</button>
</div>
);
}
export default App;Search: Focus the search field
CONSOLE
focused: INPUTBefore React 19 you had to wrap the component in forwardRef to do this; that wrapper still works but is no longer needed. Use this sparingly: giving the parent control of a child's DOM node couples them. Often a prop like autoFocus, or a callback such as onSubmit, is a cleaner interface.
useRef rules of thumb
- If the screen should change when the value changes, it is state.
- If you need a handle to a DOM node, it is a ref.
- If you need a value that lives across renders but is invisible (timer ids, previous values, "is mounted" flags), it is a ref.
- Read and write refs in handlers and effects, not while rendering.
- Do not assign to the DOM to change what React renders. Refs are for actions (focus, scroll, measure), not for content.
Common mistakes
- Reading
ref.currentin the component body on the first render, when it is stillnull. - Using a ref for something displayed, then wondering why the screen does not update.
- Changing the DOM through a ref (
ref.current.textContent = ...) instead of using state. - Writing to a ref during rendering, which breaks purity.
- Forgetting
defaultValueon an uncontrolled input that needs a starting value; usingvaluemakes it controlled again. - Using
forwardRefin new React 19 code; just takerefas a prop.
Exercise
- Build a login form that focuses the email field when the page loads (hint: an effect, covered in the next lesson, or the
autoFocusattribute) and focuses the password field when the email is submitted with Enter. - Create a stopwatch with Start, Stop and Reset buttons using a ref for the interval id.
- Make a component that remembers how many times it has rendered, using a ref. Is it a good idea to display it?
- Write an uncontrolled form with a checkbox using
defaultChecked, and print the form values on submit withFormData.
Key points
- useRef returns an object with a current property that React keeps between renders; changing it does not cause a render.
- Attach a ref to a DOM element with the ref prop to focus it, scroll to it, measure it or play a video; do this in handlers and effects, never during rendering.
- Use a ref for values that are not shown on screen, such as timer ids; use state for anything the screen displays.
- In React 19, function components receive ref as an ordinary prop, so forwardRef is no longer needed.