React Custom Hook: useResizeObserver
This challenge focuses on building a robust and reusable React custom hook, useResizeObserver. This hook will leverage the browser's ResizeObserver API to efficiently track changes in an element's dimensions and provide those dimensions to your React components. This is incredibly useful for responsive design, dynamic layout adjustments, and performance optimizations where you need to react to visual changes in the DOM.
Problem Description
Your task is to create a custom React hook named useResizeObserver that accepts a React RefObject pointing to a DOM element. The hook should:
- Observe the element: Use the
ResizeObserverAPI to observe the dimensions (width and height) of the referenced DOM element. - Return dimensions: Provide the current
widthandheightof the observed element as state within the hook. - Update on resize: Automatically update the returned dimensions whenever the observed element's size changes.
- Handle cleanup: Ensure that the
ResizeObserveris properly disconnected when the component unmounts or the ref changes to prevent memory leaks.
Key Requirements:
- The hook should be written in TypeScript.
- It should accept a
RefObject<Element>as an argument. - It should return an object containing
widthandheight(both numbers). - The hook should return
0for bothwidthandheightinitially, or until the element is rendered and observed. - The
ResizeObservershould be initialized with a callback that updates the hook's state. - The observer should be disconnected in a
useEffectcleanup function.
Expected Behavior:
When a component uses this hook and passes a ref to an element, the hook should return the element's current dimensions. As the element is resized (e.g., by the browser window resizing, or CSS changes), the hook should automatically re-render the consuming component with the updated width and height.
Edge Cases to Consider:
- Element not yet rendered: The initial state should reflect that the element is not yet measured.
- Ref changes: If the ref object itself is updated to point to a different element, the old observer should be disconnected and a new one created.
- Element removed: If the referenced element is removed from the DOM, the observer should be disconnected.
Examples
Example 1: Basic Usage
Let's imagine a component that displays the dimensions of a div.
import React, { useRef } from 'react';
import useResizeObserver from './useResizeObserver'; // Assuming your hook is in this file
function ResizableBox() {
const boxRef = useRef<HTMLDivElement>(null);
const { width, height } = useResizeObserver(boxRef);
return (
<div>
<div
ref={boxRef}
style={{
width: '50%', // Example responsive width
height: '200px',
backgroundColor: 'lightblue',
resize: 'both', // Allow manual resizing for testing
overflow: 'auto',
}}
>
Resize me!
</div>
<p>Current Dimensions: {width}px x {height}px</p>
</div>
);
}
Input to the hook: useRef<HTMLDivElement>(null) pointing to the div.
Expected Output (initial render):
The useResizeObserver hook would initially return { width: 0, height: 0 }.
Expected Output (after render and resize):
Assuming the div renders and has a computed width of 400px and a height of 200px:
{ width: 400, height: 200 }
Explanation:
The hook attaches a ResizeObserver to the div. When the element is painted and its dimensions are known, the observer's callback fires, updating the hook's internal state. The ResizableBox component re-renders with the new dimensions.
Example 2: Handling Null Ref
If the ref is initially null or null is passed.
import React from 'react';
import useResizeObserver from './useResizeObserver';
function ComponentWithNullRef() {
// Intentionally not assigning a ref here
const { width, height } = useResizeObserver(null as unknown as React.RefObject<HTMLDivElement>);
return (
<div>
<p>Dimensions for null ref: {width}px x {height}px</p>
</div>
);
}
Input to the hook: null (or a ref object that is currently null).
Expected Output:
{ width: 0, height: 0 }
Explanation:
The hook should gracefully handle a null ref by not attempting to create an observer and by returning default values.
Constraints
- The hook must be written in TypeScript.
- It should not rely on any external libraries for the
ResizeObserverfunctionality. - Performance is important: the
ResizeObservercallback should be efficient, only updating state when necessary. - The hook should work correctly within React's lifecycle (e.g.,
useEffectfor setup and cleanup).
Notes
- The
ResizeObserverAPI provides anentriesarray in its callback. You will likely want to accessentry.contentRect.widthandentry.contentRect.height. - Consider how you will handle the initial state before the element has been measured.
- Make sure to correctly type the
RefObjectargument. - Think about the
useEffectdependency array to ensure the observer is re-attached if the ref target changes (thoughRefObjectitself doesn't typically change in a way that would trigger auseEffectif its.currentproperty is what's being observed). The primary concern is proper cleanup when the component unmounts.