hooks/elementSize.jsxjavascript
/*
  Observes an element's content-box size via ResizeObserver. Used wherever a
  component needs to react to its own real rendered size rather than the CSS
  viewport (Stage.jsx sizing the whole app; GlobePage.jsx sizing the WebGL
  canvas, which react-globe.gl otherwise defaults to window.innerWidth/
  innerHeight and never updates on resize).
*/

import { useCallback, useLayoutEffect, useState } from "react";

/**
 * @param {{width: number, height: number}} [initialSize] - Value returned before the
 *   observer's first callback fires. Defaults to {0,0}; pass a best-guess (e.g.
 *   window.innerWidth/innerHeight) when the element is expected to roughly fill the
 *   viewport, to avoid a one-frame flash at size 0.
 * @returns {Array} A `[ref, size]` tuple: a callback ref to attach to the observed element,
 *   and its current `{width, height}` content-box size in CSS px.
 */
export const useElementSize = (initialSize = { width: 0, height: 0 }) => {
  // A callback ref (not a plain useRef) so the observer setup below re-runs
  // whenever the underlying DOM node actually changes - including from null
  // to a real element on a *later* commit than the component's first. A
  // plain ref + `useLayoutEffect(..., [])` would only ever check
  // `ref.current` once, on mount: if the consumer conditionally renders
  // `null` on its first render (e.g. GlobePage, while content is still
  // loading) and only mounts the real element once state resolves, that
  // one-shot effect sees `null` and never gets a second chance.
  const [node, setNode] = useState(null);
  const ref = useCallback((el) => setNode(el), []);

  const [size, setSize] = useState(initialSize);

  useLayoutEffect(() => {
    if (!node) return undefined;

    const observer = new ResizeObserver(([entry]) => {
      const { width, height } = entry.contentRect;
      setSize({ width, height });
    });
    observer.observe(node);

    return () => observer.disconnect();
  }, [node]);

  return [ref, size];
};