Callback Refs

The ref attribute also accepts a function. React 7,897 calls it with the DOM node when the node is attached, and since React 19 the function may return a cleanup function that React calls when the node is detached. A ref object cannot tell you when it was filled; a callback ref is the notification. That decides every measuring problem: useRef plus a read during render fails outright, because current is still null while the JSX is being produced.

A callback ref that measures the node it receivesHTML
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@8.0.5/babel.min.js"></script>
<script type="importmap">{"imports":{"react":"https://esm.sh/react@19.3.0?dev",
"react-dom/client":"https://esm.sh/react-dom@19.3.0/client?dev"}}</script>
<div id="root"></div><script type="text/babel" data-type="module">
  import React, { useState, useCallback } from 'react';
  import { createRoot } from 'react-dom/client';
  function MeasureExample() {
    const [rect, setRect] = useState(null);
    const measuredRef = useCallback(node => {
      if (node) setRect(node.getBoundingClientRect());
      return () => { setRect(null); };          // React 19: runs when the node detaches
    }, []);
    return <div style={{font: '16px system-ui'}}>
      <h1 ref={measuredRef}>Hello, world</h1>
      {rect && <p>The heading is {Math.round(rect.width)}px by
        {' ' + Math.round(rect.height)}px.</p>}</div>;
  }
  createRoot(document.getElementById('root')).render(<MeasureExample />);
</script>
Browser output of Listing 2.44
Browser output of 44

Wrapping the callback in useCallback with an empty dependency list matters. React compares function identity between renders, so a fresh inline arrow would make React detach the node from the old callback and attach it to the new one on every render — here, a setRect per render and an infinite loop.

Two more things follow from the signature. Returning something that is not a cleanup function is now a TypeScript error, so write ref={node => { el = node; }} with braces rather than ref={node => (el = node)}. And a callback ref is the only way to collect a list of nodes: keep a Map in a ref and have the callback run map.set(id, node) on attach and map.delete(id) on cleanup.