Stable container
The ref object keeps its identity between renders.
A ref is a persistent container for a value that React does not use to render the interface. Use it to reach a DOM node or remember mutable information without requesting another render.
useRef(initialValue) returns the same object on every render. Its one writable property, current, starts with the initial value and can later hold any JavaScript value.
The ref object keeps its identity between renders.
A JSX ref can point to a browser DOM node.
Changing current does not trigger a render.
import { useRef } from "react";
function Example() {
const visitCount = useRef(0);
function recordVisit() {
visitCount.current += 1;
console.log(visitCount.current); // Updated immediately
}
return <button onClick={recordVisit}>Record visit</button>;
}
Do not read or write ref.current while rendering. Rendering should stay pure. Read or change refs in event handlers or Effects instead. Predictable one-time initialization is the narrow exception.
Sometimes code must perform an imperative browser action such as focusing an input, scrolling an element into view, measuring its size, or controlling media. Pass a ref object to a built-in element's ref attribute so React can place that DOM node in current.
import { useRef } from "react";
export default function SearchForm() {
const inputRef = useRef(null);
function focusSearch() {
inputRef.current?.focus();
}
return (
<form>
<label htmlFor="search">Search notes</label>
<input id="search" ref={inputRef} />
<button type="button" onClick={focusSearch}>
Focus search
</button>
</form>
);
}
useRef(null).There is no DOM node during the first render, so null is the useful initial value.
<input ref={inputRef} /> tells React which node the ref should track.
In an event handler, inputRef.current refers to the input. React sets it back to null when that node is removed.
Keep React in charge of structure. Non-destructive actions such as focus, scroll, and measurement are normal ref uses. Avoid manually removing nodes or rewriting content that React renders because the next commit may conflict with those changes.
A ref can hold more than a DOM node. It is useful for information that must survive renders but does not describe what the user sees, such as a timeout ID, an external library instance, or bookkeeping used by an event handler.
import { useRef, useState } from "react";
export default function DraftSaver() {
const [status, setStatus] = useState("Ready");
const timeoutRef = useRef(null);
function scheduleSave() {
clearTimeout(timeoutRef.current);
setStatus("Waiting to save");
timeoutRef.current = setTimeout(() => {
// Send the latest draft to a server here.
setStatus("Saved");
}, 1000);
}
return (
<section>
<button onClick={scheduleSave}>Schedule save</button>
<p aria-live="polite">{status}</p>
</section>
);
}
The user-visible status is state because the paragraph must update. The timeout ID is a ref because React never renders it. Both values persist, but only the state setter requests a new render.
Ask: would changing this value alone need to update the screen? If not, a ref may fit.
current, not the ref binding.The ref object is stable. Store the changing value in its current property.
If a ref tracks a timer, subscription, or third-party object that can outlive the component, cancel or dispose of it in an Effect cleanup.
State and refs both retain values between renders. The deciding question is whether changing the value should make React calculate and commit a new interface.
setCount.ref.current.function BrokenCounter() {
const countRef = useRef(0);
function handleClick() {
countRef.current += 1;
// No render was requested, so the button text stays stale.
}
return (
<button onClick={handleClick}>
Count: {countRef.current}
</button>
);
}
Decision rule: if a value affects rendered output, store it in state. If it only supports an event, browser API, or external system behind the scenes, store it in a ref.
Keep this model: useRef gives a component a stable, mutable container. React can fill it with a DOM node, or your code can keep a non-rendering value in it. Updating current is immediate, but only state updates tell React to render again.
ref.current not update text already displayed on the page?
React does not track ref mutations as rendering updates. The value changes immediately, but no new render is requested, so existing DOM output remains unchanged.
null?
The DOM node does not exist during the first render. React assigns the node to current during the commit and returns it to null when the node is removed.
Use state because changing the message must update rendered JSX. A ref is better for related bookkeeping that does not appear in the interface, such as a timeout ID.