mrkeyoor.com_
Sat 08 Aug 17:41 UTC
npmWeb Frontendupdated 08 Aug 2026

react-use-measure

react-use-measure is a small React hook that observes one HTML or SVG element and returns its viewport-relative bounding rectangle. It combines ResizeObserver with window resize events and, optionally, captured scroll events from every scrollable ancestor, so width, height, x, y, top, right, bottom, and left update when layout moves. Options cover debouncing, an injected ResizeObserver polyfill, and unscaled HTML offset dimensions. The current return tuple also includes a manual refresh callback, even though the README's shorter signature still shows only ref and bounds.

Verdict

A tiny, useful answer when reactive position and size are both required, especially inside scroll containers. Use a narrower ResizeObserver hook when width and height are all you need, and plan for zero first-render values.

API stability4/5The hook's core ref-and-bounds contract is small, and version 2.1.7 still supports React back to 16.13 with no framework-specific machinery. Debounce, scroll, polyfill, and offsetSize are optional additions. One documentation mismatch lowers confidence: the README declares a two-item tuple and MutableRefObject, while the shipped TypeScript and source return a callback ref, bounds, and forceRefresh. Consumers following types get the current contract.
Docs3/5The README quickly explains the coordinate problem, zero initial bounds, all options, polyfill injection, and multi-ref composition. It is enough to start in minutes. It does not cover SSR, test simulation, transform limitations beyond offsetSize, observer frequency, or accessibility-adjacent layout concerns, and its displayed return type is stale compared with the published declaration's three-item tuple and callback ref.
Maintenance3/5Version 2.1.7 was published and the repository last pushed on January 30, 2025. The repository is not archived, has 985 stars, and GitHub reports 23 open issues and pull requests, but there has been no push in roughly 18 months as of this guide. The package is small and current code tests with React 19 and Playwright, yet users should treat it as mature and quiet rather than actively evolving.
Ecosystem4/5The npm download API reports 6,619,613 downloads in the latest week, substantial use for a one-hook package. It works with both HTML and SVG, exposes standard rectangle fields, and recommends established helpers for missing ResizeObserver and merged refs. Integration is intentionally generic rather than plugin-driven; charting, animation, overlay, and canvas libraries consume the numeric bounds without dedicated adapters.

Use it if

  • A tooltip, overlay, chart, canvas, or pointer interaction needs reactive width, height, and viewport coordinates from one element
  • The element can move because a nested scroll container moves, and observing scroll ancestors is worth the extra listeners
  • You need the same hook for HTML and SVG elements and can tolerate zero bounds before the first client measurement
  • A transformed layout needs offsetWidth and offsetHeight as an alternative to scaled bounding-box dimensions
Skip it if

Setup reality

Install react-use-measure with React 16.13 or newer; ReactDOM is an optional peer. The package has no runtime dependencies, includes ESM and CommonJS exports plus TypeScript declarations, and is about 1.3 KB gzipped. The hook is client-oriented but safe to render on the server: it substitutes an inert ResizeObserver class while window is absent, and initial bounds remain zeros. Hydration does not give you real geometry until the callback ref attaches and effects run, so do not branch server markup on measured width unless you accept a post-hydration layout change. Modern browsers provide ResizeObserver; older or unusual environments need an injected polyfill. The returned ref is a function, not a MutableRefObject despite the README's older API text. Attach it to exactly the element being measured. If another library needs that element, merge callback refs rather than replacing this one. The installed type declares a three-item tuple: ref, bounds, and forceRefresh. Measurements use getBoundingClientRect and therefore report viewport coordinates and transformed dimensions. offsetSize substitutes offsetWidth and offsetHeight for HTML elements only. Enable scroll:true when nested or page scrolling must update coordinates; the source then attaches passive capture listeners to every ancestor whose computed overflow is auto or scroll, plus window scroll, which can be noisy in deep layouts. Debounce resize and scroll independently for expensive children, understanding that visual overlays will lag during the delay. The hook always observes element resize and window resize, cleans listeners on unmount, and freezes each rectangle. In tests, jsdom generally needs a ResizeObserver stub plus explicit callback triggering; a rendered zero rectangle is normal until you simulate measurement. The latest release and push were January 2025, so verify React and browser regressions locally rather than assuming recent CI activity.

Patterns

Measure an element after layoutmeasure-element-bounds

import useMeasure from 'react-use-measure'

export function Card() {
  const [measureRef, bounds] = useMeasure()
  return (
    <section ref={measureRef}>
      {Math.round(bounds.width)} x {Math.round(bounds.height)}
    </section>
  )
}

Every value is zero on the first render. The callback ref and observer schedule the real rectangle after the DOM node exists.

Size a chart from its containerrender-responsive-chart

const [containerRef, bounds] = useMeasure({ debounce: 50 })

return (
  <div ref={containerRef} className="chart-shell">
    {bounds.width > 0 && (
      <Chart width={bounds.width} height={Math.max(240, bounds.width * 0.5)} />
    )}
  </div>
)

Guard the zero-width first pass, and debounce only if chart rerenders are expensive enough to justify delayed resizing.

Update coordinates while ancestors scrolltrack-nested-scroll

const [targetRef, bounds] = useMeasure({
  scroll: true,
  debounce: { scroll: 8, resize: 80 },
})

return <div ref={targetRef}>Tracked target</div>

scroll:true attaches captured passive listeners to scrollable ancestors and window. Avoid it for static layouts because frequent scroll updates rerender the owner.

Inject a ResizeObserver polyfillinject-resize-observer

import { ResizeObserver } from '@juggle/resize-observer'
import useMeasure from 'react-use-measure'

const [ref, bounds] = useMeasure({ polyfill: ResizeObserver })

Install @juggle/resize-observer separately. Without a native or injected constructor, the hook throws in the browser.

Read unscaled HTML width and heightignore-scale-for-size

const [ref, bounds] = useMeasure({ offsetSize: true })

return (
  <div style={{ transform: 'scale(0.75)' }}>
    <div ref={ref}>Layout size: {bounds.width}</div>
  </div>
)

offsetSize uses offsetWidth and offsetHeight only for HTMLElement. Position and edge fields still come from getBoundingClientRect, and SVG size remains transformed.

Share the measured node with another refmerge-element-refs

import mergeRefs from 'react-merge-refs'

const localRef = useRef<HTMLDivElement>(null)
const [measureRef, bounds] = useMeasure()

return <div ref={mergeRefs([localRef, measureRef])}>{bounds.width}</div>

react-merge-refs is a separate package. Replacing measureRef with localRef prevents the hook from observing and cleaning up the node.

Refresh after an external layout changeforce-manual-measurement

const [ref, bounds, forceRefresh] = useMeasure()

useEffect(() => {
  const animation = document.fonts?.ready.then(forceRefresh)
  return () => void animation
}, [forceRefresh])

The third tuple item exists in the published types and source but is missing from the README signature. ResizeObserver often makes manual refresh unnecessary.

Position a fixed overlay from viewport boundsposition-overlay

const [anchorRef, bounds] = useMeasure({ scroll: true })

return (
  <>
    <button ref={anchorRef}>Details</button>
    <div style={{ position: 'fixed', top: bounds.bottom + 8, left: bounds.left }}>
      Overlay
    </div>
  </>
)

Bounds are viewport-relative, which matches position:fixed. Portals, clipping, viewport edges, and focus handling still need an overlay library or application logic.

Convert pointer coordinates to element coordinatescalculate-local-pointer

const [surfaceRef, bounds] = useMeasure({ scroll: true })

function onPointerMove(event: React.PointerEvent) {
  const local = {
    x: event.clientX - bounds.left,
    y: event.clientY - bounds.top,
  }
  updateCursor(local)
}

return <div ref={surfaceRef} onPointerMove={onPointerMove} />

clientX and clientY are viewport-relative like getBoundingClientRect. CSS scale changes the measured coordinate system, so map again for an unscaled canvas model.

Observe an SVG elementmeasure-svg-element

const [svgRef, bounds] = useMeasure()

return (
  <svg width="100%" height="240">
    <g ref={svgRef}>
      <circle cx="80" cy="80" r="40" />
    </g>
    <text y="220">{bounds.width.toFixed(1)}</text>
  </svg>
)

The callback accepts SVGElement, but offsetSize has no effect because offsetWidth and offsetHeight are HTMLElement-only.

Defer geometry-dependent UI until measuredavoid-hydration-branching

const [ref, bounds] = useMeasure()
const measured = bounds.width > 0 || bounds.height > 0

return (
  <div ref={ref}>
    <Content />
    {measured && <GeometryDependentControls bounds={bounds} />}
  </div>
)

Server and first client render both see zeros. Keeping their stable content equal avoids using unknown geometry to produce mismatched markup.

Tune scroll and resize updates independentlydebounce-separately

const [ref, bounds] = useMeasure({
  scroll: true,
  debounce: {
    scroll: 16,
    resize: 120,
  },
})

Debouncing reduces renders but makes bounds temporarily stale. Overlays usually need a shorter scroll delay than expensive resize-driven charts.

Alternatives

PackageRegistryPick it when
use-resize-observernpmYou mainly need element width and height with rounding and ref options, not scroll-aware viewport coordinates
react-resize-detectornpmYou prefer a hook or component API with skip, refresh mode, and refresh-rate controls
@react-hook/resize-observernpmA minimal hook around ResizeObserver is enough and position tracking belongs elsewhere