ref
Get the underlying DOM node for an element, passed as an ordinary prop since React 19.
<div ref={refObject | ((node: Node | null) => (() => void) | void)} />Parameters
| Parameter | Type | Description |
|---|---|---|
$refoptional | RefObject | ref callback | A ref object from useRef or createRef, whose current property React fills with the node, or a callback React calls with the node. |
$node | Node | null | Argument passed to a ref callback: the DOM node when the ref attaches. Pre-19 behaviour, or a callback that returns no cleanup, also gets called with null on detach. |
Return value
(() => void) | voidA ref callback may return a cleanup function, which React calls when the ref detaches. If it returns nothing, React calls the callback again with null on detach instead — a behaviour that will be removed in a future version.
Errors
Reading ref.current before the element has mounted gives null, so calling a method on it such as focus() throws a TypeError.
Changelog
- PHP 19.0Function components receive ref as a normal prop; forwardRef is no longer needed and will be deprecated in a future release.
- PHP 19.0ref callbacks may return a cleanup function, which React calls on detach instead of calling the callback again with null. Returning anything else is now a TypeScript error.
- PHP 19.0String refs (ref="myInput") were removed.
See also
ref gives you the DOM node React created for an element, so you can call browser APIs that React has no declarative equivalent for.
When to reach for it
- Focus, text selection,
scrollIntoView, media playback controls. - Measuring layout, or wiring a node into a non-React library such as a chart or map.
Two shapes
A ref object from useRef is the common case: React writes the node into .current after commit. A ref callback is the escape hatch — React calls it with the node on attach, and calls the cleanup function you return on detach, which is where you unsubscribe observers and listeners.
What trips people up
current is null during the first render. Reading it inside the render body, or in an event handler that can fire before mount, gives you nothing; read it in effects and handlers instead.
An inline arrow function is a different callback on every render, so React tears the ref down and sets it back up after each one. Wrap it in useCallback when it does real work.
In React 19 a function component can simply accept ref in its props and hand it to a DOM element. Class components still receive the instance, not a prop.
Example
When to use it
- Focusing a search input when a dialog opens
- Scrolling a newly added chat message into view
- Observing an element with ResizeObserver and disconnecting on unmount
- Handing a container node to a third-party charting library
Frequently asked questions
Why is ref.current null?
Because refs are attached after the DOM is committed, so current is null during the first render and in anything that runs before mount. Read it inside an effect or an event handler, and guard with ref.current?.focus() if the element can be conditionally rendered.
Do I still need forwardRef in React 19?
No — function components accept ref as a normal prop, so destructure it out of props and pass it down. forwardRef still works but is slated for deprecation; class components are unchanged.
Why does my ref callback fire twice on mount?
Strict Mode runs an extra development-only setup and cleanup cycle to prove your cleanup undoes your setup. If a repeated setup causes trouble, the fix is to return a cleanup function that actually reverses it, not to remove Strict Mode.
More examples
Migrating off forwardRef
Both components behave identically, but the React 19 version has no wrapper: ref arrives in the props object and is forwarded to the input like any other prop. The forwardRef import can be deleted. Refs on class components are unaffected — those still resolve to the instance and are not passed as props.
// Before React 19
import { forwardRef, useRef } from "react";
const OldInput = forwardRef(function OldInput({ placeholder }, ref) {
return <input ref={ref} placeholder={placeholder} />;
});
// React 19: ref is just another prop
function NewInput({ placeholder, ref }) {
return <input ref={ref} placeholder={placeholder} />;
}
export default function Form() {
const ref = useRef(null);
return (
<form onSubmit={(e) => e.preventDefault()}>
<NewInput placeholder="Email" ref={ref} />
<button onClick={() => ref.current.focus()}>Focus</button>
</form>
);
}A callback ref that owns a subscription
The callback sets up a ResizeObserver when the node attaches and returns a cleanup that disconnects it. Because a cleanup function is returned, React 19 never calls the callback again with null, so the null guard is dead code on 19 and only matters if the component also ships to older versions. Without useCallback the observer would be torn down and rebuilt after every state update the observer itself causes.
import { useCallback, useState } from "react";
export default function Measured() {
const [width, setWidth] = useState(null);
const measure = useCallback((node) => {
if (!node) return; // only reached on React 18 and older
const observer = new ResizeObserver(([entry]) => {
setWidth(Math.round(entry.contentRect.width));
});
observer.observe(node);
return () => observer.disconnect();
}, []);
return (
<div ref={measure}>
Width: {width === null ? "measuring" : width + "px"}
</div>
);
}
Discussion