React course menu
React useRef Hook: Accessing DOM Elements and Storing Values
Learn useRef: referencing DOM elements to focus inputs and scroll, storing values that persist without re-rendering such as timer ids, the difference between refs and state, passing refs to your own components, and when not to use refs.
On this page
useRef gives a component a box, { current: ... }, whose contents persist between renders but do not cause a re-render when changed. It has two main uses: getting hold of DOM elements, and remembering values that are not shown on screen.
Referencing DOM elements#
Pass a ref to an element's ref attribute, and React puts the DOM node in ref.current. You can then call browser methods such as focus(), scrollIntoView() or play():
import { useRef } from "react";
export default function App() {
const inputRef = useRef(null);
return (
<div>
<input ref={inputRef} placeholder="Search products" />
<button onClick={() => inputRef.current.focus()} style={{ marginLeft: 8 }}>
Focus search
</button>
</div>
);
}ref.current is set after React creates the element, so read it in event handlers or effects, never during rendering.
Storing values without re-rendering#
A stopwatch needs to remember its interval id to stop it later. The id is never displayed, so it belongs in a ref, not in state:
import { useRef, useState } from "react";
export default function App() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function start() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => setNow(Date.now()), 10);
}
function stop() {
clearInterval(intervalRef.current);
}
const seconds = startTime && now ? (now - startTime) / 1000 : 0;
return (
<div>
<p style={{ fontSize: 24, fontFamily: "monospace" }}>{seconds.toFixed(2)} s</p>
<button onClick={start}>Start</button>{" "}
<button onClick={stop}>Stop</button>
</div>
);
}Ref or state?#
The demo below makes the difference visible: the ref's count increases in memory, but the screen only shows the new number when something else causes a re-render.
import { useRef, useState } from "react";
export default function App() {
const refCount = useRef(0);
const [stateCount, setStateCount] = useState(0);
return (
<div>
<button onClick={() => (refCount.current += 1)}>ref +1</button>{" "}
<button onClick={() => setStateCount(stateCount + 1)}>state +1</button>
<p>ref: {refCount.current} · state: {stateCount}</p>
</div>
);
}Click "ref +1" three times: the screen still shows 0. Click "state +1": the re-render reveals the ref's value of 3.
Scrolling to an element#
import { useRef } from "react";
export default function App() {
const bottomRef = useRef(null);
const messages = Array.from({ length: 12 }, (_, i) => `Message ${i + 1}`);
return (
<div>
<button onClick={() => bottomRef.current.scrollIntoView({ behavior: "smooth", block: "nearest" })}>
Jump to latest
</button>
<div style={{ height: 100, overflowY: "auto", border: "1px solid #ddd", marginTop: 8, padding: 6 }}>
{messages.map((m) => <p key={m} style={{ margin: "4px 0" }}>{m}</p>)}
<div ref={bottomRef} />
</div>
</div>
);
}Refs to your own components#
In React 19, ref can be passed to your own function components like any other prop, and forwarded to an element inside:
import { useRef } from "react";
function TextField({ label, ref }) {
return (
<label>
{label} <input ref={ref} />
</label>
);
}
export default function App() {
const nameRef = useRef(null);
return (
<div>
<TextField label="Name" ref={nameRef} />
<button onClick={() => nameRef.current.focus()} style={{ marginLeft: 8 }}>Focus</button>
</div>
);
}Older code (React 18 and before) wraps such components in forwardRef, which you will still see in many projects.
Don't overuse refs#
React manages the DOM for you. Use refs for things React has no prop for (focus, scrolling, measuring, media playback, third-party libraries), not to change text, classes or styles directly. Those should come from state and JSX; editing the DOM by hand gets overwritten on the next render.
Common mistakes#
Practice#
Practice exercise
Make a search form where pressing a "Clear" button empties the input (controlled with state) and then puts the cursor back in it.
Show hint
Keep the text in state and the input in a ref. In the button's handler, call setText("") and then inputRef.current.focus().
Show solution
import { useRef, useState } from "react";
export default function App() {
const [text, setText] = useState("");
const inputRef = useRef(null);
function clear() {
setText("");
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} value={text} onChange={(e) => setText(e.target.value)} placeholder="Search" />
<button onClick={clear} style={{ marginLeft: 8 }}>Clear</button>
</div>
);
}Key takeaway#
Finished this lesson?