Rooks
UI & Layout

useMeasure

About

Measures both the inner and outer dimensions of any DOM element in a performant way and updates when dimensions change. Uses ResizeObserver for efficient size change detection, providing comprehensive measurement data including inner dimensions (clientWidth/clientHeight), outer dimensions (offsetWidth/offsetHeight), and scroll dimensions for both inner and outer measurements.

Examples

Basic usage

import React from "react";
import { useMeasure } from "rooks";
 
export default function App() {
  const [ref, measurements] = useMeasure();
  
  return (
    <div
      ref={ref}
      style={{
        width: 200,
        height: 150,
        border: "5px solid #ccc",
        overflow: "auto",
        padding: "10px",
        margin: "20px"
      }}
    >
      <div style={{ width: 300, height: 200 }}>
        <h4>Inner Measurements:</h4>
        <p>Width: {measurements.innerWidth}, Height: {measurements.innerHeight}</p>
        <p>Scroll: {measurements.innerScrollWidth} x {measurements.innerScrollHeight}</p>
        
        <h4>Outer Measurements:</h4>
        <p>Width: {measurements.outerWidth}, Height: {measurements.outerHeight}</p>
        <p>Scroll: {measurements.outerScrollWidth} x {measurements.outerScrollHeight}</p>
        
        <p>This content is larger than the container to demonstrate scroll measurements.</p>
      </div>
    </div>
  );
}

With callback and debouncing

import React, { useState } from "react";
import { useMeasure } from "rooks";
 
export default function App() {
  const [measurementHistory, setMeasurementHistory] = useState([]);
  
  const [ref, measurements] = useMeasure({
    debounce: 100,
    onMeasure: (newMeasurements) => {
      setMeasurementHistory(prev => [
        ...prev.slice(-4), // Keep last 5 measurements
        newMeasurements
      ]);
    }
  });
  
  return (
    <div>
      <textarea
        ref={ref}
        placeholder="Resize this textarea to see measurements change..."
        style={{ 
          width: "100%", 
          minHeight: 100, 
          resize: "both",
          border: "3px solid #007acc",
          padding: "8px"
        }}
      />
      
      <div>
        <h3>Current Measurements:</h3>
        <div style={{ display: "flex", gap: "20px" }}>
          <div>
            <h4>Inner:</h4>
            <p>{measurements.innerWidth} x {measurements.innerHeight}</p>
            <p>Scroll: {measurements.innerScrollWidth} x {measurements.innerScrollHeight}</p>
          </div>
          <div>
            <h4>Outer:</h4>
            <p>{measurements.outerWidth} x {measurements.outerHeight}</p>
            <p>Scroll: {measurements.outerScrollWidth} x {measurements.outerScrollHeight}</p>
          </div>
        </div>
        
        <h3>Measurement History:</h3>
        {measurementHistory.map((measurement, index) => (
          <div key={index}>
            <small>
              {index + 1}: Inner {measurement.innerWidth}x{measurement.innerHeight}, 
              Outer {measurement.outerWidth}x{measurement.outerHeight}
            </small>
          </div>
        ))}
      </div>
    </div>
  );
}

Responsive component layout with inner/outer awareness

import React from "react";
import { useMeasure } from "rooks";
 
export default function ResponsiveCard() {
  const [ref, measurements] = useMeasure();
  
  // Adjust layout based on container size
  const isCompact = measurements.innerWidth < 300;
  const showDetails = measurements.innerHeight > 150;
  const hasThickBorder = (measurements.outerWidth - measurements.innerWidth) > 10;
  
  return (
    <div
      ref={ref}
      style={{
        width: "100%",
        maxWidth: 400,
        height: "100%",
        maxHeight: 300,
        border: hasThickBorder ? "8px solid #ddd" : "1px solid #ddd",
        borderRadius: "8px",
        padding: "16px",
        display: "flex",
        flexDirection: isCompact ? "column" : "row",
        gap: "12px"
      }}
    >
      <div style={{ flex: 1 }}>
        <h3>Responsive Card</h3>
        <p>Inner: {measurements.innerWidth}x{measurements.innerHeight}</p>
        <p>Outer: {measurements.outerWidth}x{measurements.outerHeight}</p>
        <p>Layout: {isCompact ? "Compact" : "Wide"}</p>
        <p>Border: {hasThickBorder ? "Thick" : "Thin"}</p>
      </div>
      
      {showDetails && (
        <div style={{ 
          flex: isCompact ? "none" : 1,
          backgroundColor: "#f5f5f5",
          padding: "8px",
          borderRadius: "4px"
        }}>
          <p>Additional details shown when height > 150px</p>
          <small>
            Border + padding: {measurements.outerWidth - measurements.innerWidth}px wide, 
            {measurements.outerHeight - measurements.innerHeight}px tall
          </small>
        </div>
      )}
    </div>
  );
}

Comparing inner vs outer measurements

import React from "react";
import { useMeasure } from "rooks";
 
export default function MeasurementComparison() {
  const [ref, measurements] = useMeasure();
  
  const borderWidth = (measurements.outerWidth - measurements.innerWidth) / 2;
  const borderHeight = (measurements.outerHeight - measurements.innerHeight) / 2;
  
  return (
    <div style={{ padding: "20px" }}>
      <div
        ref={ref}
        style={{
          width: 300,
          height: 200,
          border: "15px solid #ff6b6b",
          padding: "25px",
          backgroundColor: "#4ecdc4",
          overflow: "auto"
        }}
      >
        <div style={{ width: 400, height: 250, backgroundColor: "#45b7d1" }}>
          <h3>Measurement Breakdown</h3>
          
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px" }}>
            <div>
              <h4>Inner (Content Area):</h4>
              <p>Width: {measurements.innerWidth}px</p>
              <p>Height: {measurements.innerHeight}px</p>
              <p>Scroll Width: {measurements.innerScrollWidth}px</p>
              <p>Scroll Height: {measurements.innerScrollHeight}px</p>
            </div>
            
            <div>
              <h4>Outer (Including Border/Padding):</h4>
              <p>Width: {measurements.outerWidth}px</p>
              <p>Height: {measurements.outerHeight}px</p>
              <p>Scroll Width: {measurements.outerScrollWidth}px</p>
              <p>Scroll Height: {measurements.outerScrollHeight}px</p>
            </div>
          </div>
          
          <div style={{ marginTop: "10px" }}>
            <h4>Calculated Border + Padding:</h4>
            <p>Horizontal: {borderWidth.toFixed(1)}px each side</p>
            <p>Vertical: {borderHeight.toFixed(1)}px each side</p>
          </div>
        </div>
      </div>
    </div>
  );
}

Disabled measurements

import React, { useState } from "react";
import { useMeasure } from "rooks";
 
export default function ConditionalMeasurement() {
  const [measurementEnabled, setMeasurementEnabled] = useState(true);
  
  const [ref, measurements] = useMeasure({
    disabled: !measurementEnabled
  });
  
  return (
    <div>
      <button onClick={() => setMeasurementEnabled(!measurementEnabled)}>
        {measurementEnabled ? "Disable" : "Enable"} Measurements
      </button>
      
      <div
        ref={ref}
        style={{
          width: 250,
          height: 150,
          border: "2px solid #007acc",
          margin: "20px 0",
          padding: "10px"
        }}
      >
        <p>Measurements {measurementEnabled ? "enabled" : "disabled"}</p>
        <div>
          <h4>Inner:</h4>
          <p>{measurements.innerWidth} x {measurements.innerHeight}</p>
          <h4>Outer:</h4>
          <p>{measurements.outerWidth} x {measurements.outerHeight}</p>
        </div>
      </div>
    </div>
  );
}

Arguments

ArgumentTypeDescriptionDefault value
optionsUseMeasureOptionsConfiguration options object{}

UseMeasureOptions

PropertyTypeDescriptionDefault value
debouncenumberNumber of milliseconds to debounce measurements0
disabledbooleanWhether measurements are disabledfalse
onMeasure(measurements: UseMeasureMeasurements) => voidCallback function called when dimensions changeundefined

Return

Returns a tuple [ref, measurements] with the following structure:

IndexPropertyTypeDescription
0refCallbackRefCallback ref to attach to the DOM element you want to measure
1measurementsUseMeasureMeasurementsObject containing all measurement data

UseMeasureMeasurements

PropertyTypeDescription
innerWidthnumberInner width (clientWidth) - content area width excluding scrollbars
innerHeightnumberInner height (clientHeight) - content area height excluding scrollbars
innerScrollWidthnumberTotal scrollable width (scrollWidth) - width of the entire content area
innerScrollHeightnumberTotal scrollable height (scrollHeight) - height of the entire content area
outerWidthnumberOuter width (offsetWidth) - total element width including borders, padding, and scrollbars
outerHeightnumberOuter height (offsetHeight) - total element height including borders, padding, and scrollbars
outerScrollWidthnumberTotal scrollable outer width - outer width of the entire scrollable content
outerScrollHeightnumberTotal scrollable outer height - outer height of the entire scrollable content

Notes

Browser Compatibility

  • Requires ResizeObserver support (available in all modern browsers)
  • Falls back gracefully in SSR environments
  • Warns when ResizeObserver is not available

Performance Considerations

  • Uses ResizeObserver for efficient resize detection instead of polling
  • Supports debouncing to limit measurement frequency during rapid changes
  • Automatically cleans up observers when components unmount
  • Observes with multiple box types to track both inner and outer changes

Usage Tips

  • The hook returns 0 for all dimensions until a DOM element is attached via the ref
  • Use the disabled option to temporarily stop measurements without losing the ref
  • The onMeasure callback is called after state updates, useful for logging or external state management
  • Debouncing is especially useful for expensive operations triggered by dimension changes
  • Inner measurements exclude borders and padding, while outer measurements include them
  • Use outer measurements when you need to know the total space an element occupies
  • Use inner measurements when you need to know the available content area

Inner vs Outer Measurements

  • Inner measurements (innerWidth, innerHeight): Content area only, excludes borders, padding, and scrollbars
  • Outer measurements (outerWidth, outerHeight): Complete element size including borders, padding, and scrollbars
  • Scroll measurements: Available for both inner and outer, showing total scrollable content dimensions
  • Practical use: Outer measurements are useful for layout calculations, inner measurements for content positioning

SSR Compatibility

  • Safe to use in server-side rendering environments
  • Provides appropriate warnings when ResizeObserver or window is unavailable
  • Returns default values (0) for all measurements during SSR