@knitui/hooks
useElementSize
Measure an element's content box, kept in sync via ResizeObserver (web). Returns a ref to attach, the measured width/height, and an empty rootProps (web drives measurement through the ref). The use-element-size.native sibling measures via onLayout instead. Mirrors the ref + rootProps shape of use-move. The returned ref is a CALLBACK ref, not an object ref — load-bearing. A one-shot useEffect(() => …, []) observer would capture ref.current at mount and never re-run, so it misses any element that mounts LATER (the measured node isn't in the DOM on first render). That is exactly a keepMounted={false} Collapse subtree (e.g. every collapsed Tree branch): its content mounts only on expand, so the observer never attached, the measured size stayed 0, and the panel stayed clipped to height: 0 — open in state, invisible on screen. A callback ref runs every time React attaches/detaches the node, so the observer follows the element across mounts.
Import
import { useElementSize } from "@knitui/hooks";Signature
export function useElementSize(): UseElementSizeReturnMeasuring one element is where cross-platform code usually forks: the web has
ResizeObserver, React Native has onLayout, and the two are wired up
differently — one through a ref, one through a prop. useElementSize hides that
behind a single return shape: { ref, rootProps, width, height }. Attach ref
and spread rootProps onto the element you want to measure, and the same
component compiles on both platforms.
On web, rootProps is empty and the measurement comes from a ResizeObserver
watching the node's contentRect. On native, ref exists only for parity and
rootProps carries the onLayout handler that reports the laid-out box.
Both implementations bail out when the measured box is unchanged and return the
previous state object, so a re-measure to the same value costs no render. That
matters because the consumers re-render whole subtrees off this size:
Collapse,
Spoiler and
Marquee all animate against it.
When to use it
- Animating a height or width you cannot know statically (expanding panels, clamped text).
- Laying out content against its own measured size rather than the viewport's.
- Any measurement that must survive the element re-mounting.
Notes
- Always attach both
refandrootProps. Dropping either silently breaks measurement on one platform while working on the other. - The web
refis a callback ref, not an object ref, and that is load-bearing. A one-shot mount effect would captureref.currentonce and miss any element that mounts later — akeepMounted={false}Collapsesubtree, for example, whose content only mounts on expand. The measured size would stay0and the panel would stay clipped toheight: 0. A callback ref re-attaches the observer every time React attaches the node. - Never read
.currentoff the returnedref; only spread it. - For window-level dimensions, use
useViewportSizeinstead.