React Hook to Measure Element Bounds
`react-use-measure` is a utility hook for React applications designed to precisely measure the bounding box of a referenced DOM element. It provides reactive updates to an element's dimensions and position (`x`, `y`, `width`, `height`, `top`, `right`, `bottom`, `left`), responding to changes in size, window scroll, and even nested scroll areas, which significantly differentiates it from standard `getBoundingClientRect`. This addresses a common challenge in web development where relative coordinates and offsets within complex scrollable layouts are difficult to ascertain reliably. The package is currently stable at version 2.1.7, with recent minor fix releases indicating active maintenance. It is part of the `pmndrs` ecosystem and leverages `ResizeObserver` for efficient updates. It ships with TypeScript types, promoting strong typing in projects.
Common errors
-
ReferenceError: ResizeObserver is not defined
cause The browser or runtime environment does not natively support the ResizeObserver API, and a polyfill has not been provided.fixInstall `@juggle/resize-observer` and inject it into the `useMeasure` options: `import { ResizeObserver } from '@juggle/resize-observer'; useMeasure({ polyfill: ResizeObserver });` -
TypeError: Cannot read properties of undefined (reading 'width')
cause Attempting to access `bounds.width` or other properties of the `bounds` object before the component has rendered and `useMeasure` has provided initial measurement values. `bounds` will be an empty object or have zero values initially.fixEnsure you handle the initial state where `bounds` might not have meaningful values yet, for example, by checking `if (bounds.width > 0)` or providing default values. -
TypeError: (0 , react_use_measure__WEBPACK_IMPORTED_MODULE_2__.useMeasure) is not a function
cause Attempting to import `useMeasure` as a named export with curly braces, when it is a default export of the package.fixChange the import statement from `import { useMeasure } from 'react-use-measure'` to `import useMeasure from 'react-use-measure'`.
Warnings
- gotcha The `bounds` object will initially contain zero values (`x: 0, y: 0, width: 0, height: 0, ...`) on the first render, as measurements are only possible after the element has been mounted to the DOM. Your UI should account for this initial state.
- gotcha `react-use-measure` relies on the `ResizeObserver` API. If your target environments (e.g., older browsers) do not support `ResizeObserver`, you will need to provide a polyfill.
- gotcha `useMeasure` returns its own functional ref for unmount tracking. If you need to assign another ref (e.g., from `useRef`) to the same element, you cannot do so directly. Using both will cause one to overwrite the other.
- gotcha By default, `react-use-measure` does not react to changes caused by scrolling in parent elements. To enable this behavior, you must explicitly set the `scroll` option to `true`.
Install
-
npm install react-use-measure -
yarn add react-use-measure -
pnpm add react-use-measure
Imports
- useMeasure
import { useMeasure } from 'react-use-measure'import useMeasure from 'react-use-measure'
- ResizeObserver
import { ResizeObserver } from '@juggle/resize-observer' - mergeRefs
import { mergeRefs } from 'react-merge-refs'
Quickstart
import useMeasure from 'react-use-measure';
import { useRef, useEffect } from 'react';
import { ResizeObserver } from '@juggle/resize-observer'; // Optional polyfill
function App() {
const [ref, bounds] = useMeasure({
// Optional: inject a polyfill if your target environments don't support ResizeObserver
polyfill: typeof window !== 'undefined' && 'ResizeObserver' in window ? undefined : ResizeObserver,
debounce: 100, // Debounce updates for performance
scroll: true // React to scroll changes within parent elements
});
// Example of using the bounds in a side effect
useEffect(() => {
if (bounds.width > 0) {
console.log('Element dimensions:', bounds.width, 'x', bounds.height);
}
}, [bounds]);
return (
<div style={{ padding: '20px', border: '1px solid #ccc', marginBottom: '20px' }}>
<h1>ResizeObserver Hook Example</h1>
<p>Resize your browser window or interact with the measured div.</p>
<div
ref={ref}
style={{
width: '50%',
minHeight: '100px',
background: 'lightblue',
resize: 'both', // Allows manual resizing
overflow: 'auto',
margin: '20px auto',
padding: '10px'
}}
>
<p>This div is measured:</p>
<p>Width: {bounds.width}px</p>
<p>Height: {bounds.height}px</p>
<p>Top: {bounds.top}px, Left: {bounds.left}px</p>
<div style={{ height: '200px', width: '100%', background: 'lightcoral', marginTop: '10px' }}>
Scrollable content inside.
{Array.from({ length: 50 }).map((_, i) => <div key={i}>Item {i}</div>)}
</div>
</div>
<div style={{ height: '500px', background: '#eee' }}>
A larger container to enable page scrolling and demonstrate `scroll: true`.
</div>
</div>
);
}
export default App;