Focusing inputs, measuring elements, holding timer ids and integrating non-React code all need refs.
A ref is a box whose .current you can read and write without causing a re-render:
const intervalRef = useRef(null);
intervalRef.current = setInterval(tick, 1000);Use refs for values that the rendered output doesn't depend on: timer ids, the previous value of something, an instance of a third-party library. If changing the value should update the screen, it must be state. Don't read or write ref.current during rendering (except lazy one-time initialisation); do it in handlers and effects, otherwise rendering becomes impure.
The most common use is accessing DOM nodes. Pass a ref object to a built-in element's ref attribute and React sets .current to the DOM node after it mounts (and back to null when it unmounts):
const inputRef = useRef(null);
<input ref={inputRef} />
// in a handler: inputRef.current.focus();In React 19, ref is an ordinary prop for function components: a child can accept { ref } and forward it to an inner element, so forwardRef is no longer needed. You can also pass a ref callback, ref={node => ...}, which may now return a cleanup function, useful for attaching observers per node.
Use DOM refs for things React doesn't model: focus, scroll position, measuring, media playback, integrating a chart library. Don't use them to change content React manages (adding children, changing text); React will overwrite or crash on it.
A related escape hatch is the portal. createPortal(children, domNode) from react-dom renders children into a different DOM node, like document.body, while they stay in the same place in the React tree, so context and event bubbling still work. It's how modals, tooltips and toasts escape overflow: hidden containers.
Example
import { useRef, useState } from "react";
import { createPortal } from "react-dom";
// React 19: ref is a regular prop, no forwardRef needed
function TextField({ label, ref }) {
return <label>{label} <input ref={ref} /></label>;
}
export default function Search() {
const inputRef = useRef(null);
const renders = useRef(0); // mutable, never triggers a render
const [open, setOpen] = useState(false);
return (
<>
<TextField label="Search" ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
<button onClick={() => { renders.current++; setOpen(true); }}>Help</button>
{open &&
createPortal(
<div className="modal">
<p>Type and press Enter.</p>
<button onClick={() => setOpen(false)}>Close</button>
</div>,
document.body // rendered here, but still a child in React's tree
)}
</>
);
}Remember
useRefholds a mutable.currentthat persists across renders without triggering them- Pass a ref to
ref={...}to get the DOM node; in React 19refis a normal prop for components - Don't read/write refs during render; use
createPortalto render into another DOM node
Watch out for
- Storing something in a ref that the UI displays, so the screen doesn't update when it changes.