
useMeasure and ResizeObserver: When Your UI Needs to Know Its Own Size
Often, your UI components need to react to their own dimensions, not just window size. The `ResizeObserver` API, combined with a custom React hook like `useMeasure`, is the robust solution for this, letting components adapt dynamically without complex hacks.
by Sunil Band
Your UI Needs to Look in a Mirror
We build responsive UIs all the time, typically by reacting to viewport changes or using CSS media queries. That works great for global layout adjustments. But what happens when a specific component needs to know its own rendered width or height, independently of the viewport? Maybe a chart needs to redraw its axes when its container shrinks, or a dynamically sized grid needs to adjust column counts. Trying to solve this with window.resize listeners is a mess, leading to layout thrashing and poor performance. You're trying to measure something that's only indirectly related to the global window.
This is where the ResizeObserver API shines. It's a modern browser API specifically designed to notify you when an element's content rectangle changes size. It's far more efficient than polling and precisely targets the element you care about. Combining it with a custom React hook gives us a clean, declarative way to bring this power into our components.
Why Not Just getBoundingClientRect()?
You might think element.getBoundingClientRect() would be enough. And it is, if you only need the dimensions once or on demand. But getBoundingClientRect() doesn't tell you when the element's size changes. You'd have to call it repeatedly, perhaps in a setInterval or on every window.resize event, which is exactly the kind of inefficient polling ResizeObserver was built to replace.
ResizeObserver offers a declarative, push-based model. You tell the browser which elements you want to observe, and it calls your callback only when a change occurs. This is a significant performance win, as the browser can optimize when and how often these checks are performed.
Building useMeasure
Let's build a simple useMeasure hook. It will give us a ref to attach to an element and return its width and height as state. This pattern is common for many DOM-related hooks: provide a ref, get reactive state back.
First, the basic structure:
import { useState, useRef, useLayoutEffect, useCallback } from 'react';
interface Dimensions {
width: number;
height: number;
}
type MeasureRef = (element: HTMLElement | null) => void;
export function useMeasure(): [MeasureRef, Dimensions] {
const [dimensions, setDimensions] = useState<Dimensions>({ width: 0, height: 0 });
const elementRef = useRef<HTMLElement | null>(null);
// ... observer logic will go here ...
// We need a stable ref setter to pass to the user
const setRef = useCallback((node: HTMLElement | null) => {
elementRef.current = node;
// Initial measurement might be needed here, or handle in effect
}, []);
return [setRef, dimensions];
}Now for the ResizeObserver logic. We want to initialize the observer when the component mounts and clean it up when it unmounts. useLayoutEffect is perfect here because it runs synchronously after all DOM mutations but before the browser paints. This ensures we get the most up-to-date dimensions immediately after a render that might have changed the element's size.
import { useState, useRef, useLayoutEffect, useCallback } from 'react';
interface Dimensions {
width: number;
height: number;
}
type MeasureRef = (element: HTMLElement | null) => void;
export function useMeasure(): [MeasureRef, Dimensions] {
const [dimensions, setDimensions] = useState<Dimensions>({ width: 0, height: 0 });
const elementRef = useRef<HTMLElement | null>(null);
const observerRef = useRef<ResizeObserver | null>(null); // Keep a ref to the observer
const setRef = useCallback((node: HTMLElement | null) => {
if (elementRef.current) {
// Disconnect previous observation if ref changes
observerRef.current?.disconnect();
}
elementRef.current = node;
if (elementRef.current && observerRef.current) {
// Start observing the new node
observerRef.current.observe(elementRef.current);
// Take an initial measurement
const { width, height } = elementRef.current.getBoundingClientRect();
setDimensions({ width, height });
}
}, []);
useLayoutEffect(() => {
// Create the observer instance
observerRef.current = new ResizeObserver((entries) => {
for (let entry of entries) {
// We only care about the first observed element's dimensions
const { width, height } = entry.contentRect;
setDimensions({ width, height });
}
});
// If an element is already set, start observing it
if (elementRef.current) {
observerRef.current.observe(elementRef.current);
// Take an initial measurement here too, in case setRef wasn't called yet
const { width, height } = elementRef.current.getBoundingClientRect();
setDimensions({ width, height });
}
return () => {
// Clean up the observer when the component unmounts
observerRef.current?.disconnect();
observerRef.current = null;
};
}, []); // Empty dependency array means this effect runs once on mount/unmount
return [setRef, dimensions];
}Let's break down some of the choices here:
-
useRefforelementRefandobserverRef: We need stable references to the DOM element and theResizeObserverinstance that persist across renders without causing re-renders themselves.useRefis perfect for this. -
useCallbackforsetRef: This is crucial. When we passsetRefto a child component (e.g.,<div ref={setRef}>), we wantsetRefto be a stable function reference. IfsetRefchanged on every render, it could cause unnecessary re-renders in children or break internal React optimizations. -
useLayoutEffect: As mentioned, this ensures our measurements are based on the latest DOM state before the browser paints. This prevents visual flicker where a component might render with old dimensions then immediately update to new ones. -
ResizeObservercallback: The callback receives an array ofResizeObserverEntryobjects. Each entry corresponds to an element being observed.entry.contentRectprovides theDOMRectReadOnlyfor the element, containing itswidthandheight. - Initial Measurement: We take an initial measurement in both
setRefanduseLayoutEffect. This handles cases wheresetRefmight be called before or after theuseLayoutEffectruns, ensuringdimensionsis never0,0if the element exists. - Cleanup: The return function in
useLayoutEffecthandles disconnecting theResizeObserver. This prevents memory leaks and ensures we're not observing elements that are no longer in the DOM.
Putting useMeasure to Work
Now, how do we use this in a component? It's straightforward:
```typescript jsx
import React from 'react';
import { useMeasure } from './useMeasure'; // Assuming you put the hook in useMeasure.ts
function ResizableBox() {
const [boxRef, { width, height }] = useMeasure();
const boxStyle: React.CSSProperties = {
border: '2px solid dodgerblue',
padding: '20px',
minWidth: '100px',
minHeight: '100px',
resize: 'both', // Allows manual resizing for demo
overflow: 'auto',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'aliceblue',
fontFamily: 'sans-serif',
gap: '10px'
};
return (
<div ref={boxRef} style={boxStyle}>
<h2>I'm a Resizable Box</h2>
<p>Width: {width.toFixed(2)}px</p>
<p>Height: {height.toFixed(2)}px</p>
<p>Drag me from the corner to resize!</p>
</div>
);
}
export default ResizableBox;
In this example, the `ResizableBox` component renders a `div` that can be manually resized using the `resize: 'both'` CSS property. As you drag the corner, the `useMeasure` hook detects the size changes, updates the `width` and `height` state, and causes the component to re-render, displaying the new dimensions. This is far more robust than trying to listen to window events or using an `iframe` hack.
### More Complex Use Cases
Beyond just displaying dimensions, `useMeasure` becomes powerful when you need to:
* **Dynamically adjust chart scales**: A D3 or Canvas chart can redraw itself with new scales or aspect ratios when its container changes.
* **Implement virtualized lists**: Determine how many items can fit in the viewport of a scrollable container.
* **Create responsive grid layouts without media queries**: Adjust the number of columns or item sizes based on the container's available width, rather than the global viewport.
* **Handle text overflow**: Calculate if text has overflowed its container and dynamically add an 'expand' button.
## Trade-offs and Gotchas
While `ResizeObserver` is a fantastic API, there are a couple of things to keep in mind:
* **Browser Support**: It's widely supported now (all evergreen browsers), but if you need to support very old browsers (e.g., IE11), you'll need a polyfill. For most modern web development, this isn't an issue.
* **Looping Behaviour**: If your `ResizeObserver` callback directly changes the size of the observed element, it can create an infinite loop. For example, if you read the width and then set `element.style.width = newWidth + 'px'`, that `newWidth` might trigger another resize event. The `ResizeObserver` specification explicitly addresses this by preventing infinite loops and ensuring callbacks are fired efficiently. My `useMeasure` hook only *reads* the dimensions, it doesn't modify the observed element's style, so it's safe from this particular issue.
* **`contentRect` vs `borderBoxSize` / `devicePixelContentBoxSize`**: `entry.contentRect` gives you the size of the element's content box, excluding padding, border, and margin. Newer `ResizeObserverEntry` properties like `borderBoxSize` and `devicePixelContentBoxSize` offer more granular control over what dimensions are reported, but `contentRect` is usually sufficient for most UI layout needs and has broader browser support for these specific properties.
## Wrapping up
Stop fighting with `window.resize` or trying to guess your component's dimensions. `ResizeObserver` is the declarative, performant, and robust way to get your React components to react to their own size changes. The `useMeasure` hook pattern makes integrating this powerful API into your React applications seamless. Try adding it to a dashboard component or a custom chart in your next project. You'll immediately see the benefit of your UI components truly understanding their own space.
When Your Emails Need to Be as Good as Your UI: React Email
Sending emails from your application often means dealing with HTML tables, inline styles, and inconsistent rendering across clients. It's a UX nightmare. React Email brings the component model and developer experience of React to building robust, beautiful emails.

When Your UI Needs to Break the Flat Screen: React and 3D with React Three Fiber
We've been building UIs on flat screens for decades. But what happens when you need something more? When data visualization demands depth, or an interactive product showcase needs a real sense of presence? React Three Fiber is the tool that lets you bring the full power of Three.js into your React a

When Your State Management Needs to Stop Thinking in Actions and Start Mutating
Many state management libraries force you into an 'actions and reducers' pattern. While powerful, it often adds unnecessary boilerplate for simple updates. Sometimes, you just need to directly modify state, and mutators offer a more ergonomic and intuitive approach, especially for deeply nested data


















