Callback Ref

A callback ref allows us to obtain the position and size of a DOM node. React will call that callback whenever the ref gets attached to a different node.

In this example, the callback ref will be called only when the component mounts and unmounts since the rendered

component stays present throughout any rerenders. If you want to be notified any time a component resizes, you may want to use ResizeObserver or a third-party Hook built on it.


RESETRUNFULL
<!DOCTYPE html><html><head>
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  </head><body>
    <div></div>
    <script type="text/babel">
      function MeasureExample() {
        const [width, setWidth] = React.useState(0);
        const measuredRef = React.useCallback(node => {
          if (node !== null) {
            setWidth(node.getBoundingClientRect().width);
          }
        }, []);
        return (
          <React.Fragment>
            <h1 ref={measuredRef}>Hello, world</h1>
            <h2>The above header is {Math.round(width)}px wide.</h2>
          </React.Fragment >
        );}
      const root = ReactDOM.createRoot(document.querySelector("div"));
      root.render(<MeasureExample/>);
    </script>
  </body></html>

useRef is not used in the example above because an object ref doesn't notify us about changes to the current ref value. Using a callback ref ensures that even if a child component displays the measured node later (e.g. in response to a click), we still get notified about it in the parent component and can update the measurements:

This will result in a runtime error:
Uncaught TypeError: Cannot read properties of null (reading 'getBoundingClientRect')

RESETRUNFULL
<!DOCTYPE html><html><head>
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  </head><body>
    <div></div>
    <script type="text/babel">
      function MeasureExample() {
        const wRef = React.useRef(null);
        return (
          <React.Fragment>
            <h1 ref={wRef}>Hello, world</h1>
            <h2>The above header is
              {Math.round(wRef.current.getBoundingClientRect().width)}px wide.</h2>
          </React.Fragment >
        );}
      const root = ReactDOM.createRoot(document.querySelector("div"));
      root.render(<MeasureExample/>);
    </script>
  </body></html>

We can extract the logic above into a reusable custom hook:


RESETRUNFULL
<!DOCTYPE html><html><head>
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  </head><body>
    <div></div>
    <script type="text/babel">
      function MeasureExample() {
        const [rect, ref] = useClientRect();
        return (
          <React.Fragment>
            <h1 ref={ref}>Hello, world</h1>
            {rect !== null &&
              <h2>The above header is {Math.round(rect.width)}px wide</h2>
            }
          </ React.Fragment >
        );}function useClientRect() {
        const [rect, setRect] = React.useState(null);
        const ref = React.useCallback(node => {
          if (node !== null) {
            setRect(node.getBoundingClientRect());
          }
        }, []);
        return [rect, ref];}
      const root = ReactDOM.createRoot(document.querySelector("div"));
      root.render(<MeasureExample/>);
    </script>
  </body></html>