Frontend Development 13 min read

Mastering React Ref Callbacks: When and How to Use Them

This article explains the two meanings of React refs, how ref callbacks work during component mount and unmount, and presents four practical scenarios—scrolling, measuring, portal rendering, and sharing DOM elements—complete with code examples and best‑practice tips.

KooFE Frontend Team
KooFE Frontend Team
KooFE Frontend Team
Mastering React Ref Callbacks: When and How to Use Them

In React, "ref" has two related meanings that often cause confusion: the ref object returned by the

useRef

hook (a plain JavaScript object with a

current

property) and the

ref

attribute on JSX elements used to access the underlying DOM node.

ref callback

The

ref

attribute can also accept a function, known as a ref callback, whose sole argument is the DOM element. React invokes this function at specific lifecycle moments: immediately after the element is created (argument is the DOM node) and again when the element is removed (argument is

null

).

If the ref callback is defined inline, React calls it twice on each render—first with

null

, then with the DOM element—because a new function instance is created each render, ensuring consistency when props or state change.

To avoid unnecessary calls, wrap the callback in

useCallback

or define it outside the component.

Use Cases

Ref callbacks are useful only when you need to interact directly with a DOM element. The four common scenarios are:

Performing an operation on a DOM element when it mounts or updates (e.g., scrolling or focusing).

Notifying React of DOM changes by reading element properties (e.g., size or scroll position) and storing them in state.

Placing a DOM element into state so it can be accessed during render.

Sharing a DOM element among multiple consumers.

1. Scroll an element into view on mount

You can call DOM methods such as

scrollIntoView

inside a ref callback to automatically scroll to the last list item:

<code>// On first render and on unmount there
// is no DOM element so `el` will be `null`
const scrollTo = (el) => {
  if (el) {
    el.scrollIntoView({ behavior: "smooth" });
  }
};

function List({ data }) {
  return (
    <ul>
      {data.map((d, i) => {
        const isLast = i === data.length - 1;
        return (
          <li
            key={d.name}
            ref={isLast ? scrollTo : undefined}
          >
            {d.name}
          </li>
        );
      })}
    </ul>
  );
}
</code>

Remember that React should manage DOM mutations; only non‑destructive operations like focus or scroll are appropriate here.

2. Measure a DOM element

When you need to read element dimensions, a callback ref ensures the measurement occurs even if the element appears later (e.g., after a click). Example from the old React docs:

<code>const [size, setSize] = useState();

const measureRef = useCallback((node) => {
  setSize(node.getBoundingClientRect());
}, []);

return <div ref={measureRef}>{children}</div>;
</code>

Using

useCallback

with an empty dependency array prevents the ref from changing on each render, avoiding unnecessary calls.

3. Access DOM in render (Portals)

Sometimes you need the DOM node during render, for example to create a portal. The ref callback can store the element in state and then pass it to

ReactDOM.createPortal

:

<code>function Parent() {
  const [modalElement, setModalElement] = useState(null);

  return (
    <div>
      <div id="modal-location" ref={setModalElement} />
      <Modal modalElement={modalElement}>Warning</Modal>
    </div>
  );
}

function Modal({ children, modalElement, ...props }) {
  return modalElement
    ? ReactDOM.createPortal(
        <ModalBase {...props}>{children}</ModalBase>,
        modalElement
      )
    : null;
}
</code>

This pattern is the basis of the “uncontrolled compound component” technique, where a ref callback, context, and portal work together to keep component distance short while preserving the DOM structure.

4. Share a DOM ref

When multiple consumers need the same element—e.g., measuring a

&lt;div&gt;

and passing it to a D3 or Observable Plot—you can capture the element with a callback ref and forward it:

<code>import useMeasure from "react-use-measure";
import * as Plot from "@observablehq/plot";

export function BoxPlot({ data }) {
  const [measureRef, { width, height }] = useMeasure({ debounce: 5 });
  const plotRef = useRef(null);

  useEffect(() => {
    const boxPlot = Plot.plot({
      width: Math.max(150, width),
      marks: [Plot.boxX(data)],
    });
    plotRef.current.append(boxPlot);
    return () => boxPlot.remove();
  }, [data, width]);

  const initBoxPlot = useCallback((el) => {
    plotRef.current = el;
    measureRef(el);
  }, []);

  return <div ref={initBoxPlot} />;
}
</code>

Summary

Ref callbacks are functions passed to the

ref

attribute; they receive the DOM element on mount and

null

on unmount.

Changing the ref callback triggers React to call the previous callback with

null

and the new one with the element.

They enable specific DOM operations such as scrolling, measuring, portal rendering, and sharing the element with other libraries.

Uncontrolled compound components leverage ref callbacks, context, and portals to keep component trees clean while interacting with the DOM.

frontendJavaScriptReactHooksuseCallbackuseRefref callback
KooFE Frontend Team
Written by

KooFE Frontend Team

Follow the latest frontend updates

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.