Parameters
ref: The ref of the element to observe.onResize: When usingonResize, the hook doesn't re-render on element size changes; it delegates handling to the provided callback. (default isundefined).box: The box model to use for the ResizeObserver. (default is'content-box')
Returns
- An object with the
widthandheightof the element if theonResizeoptional callback is not provided.
Polyfill
The useResizeObserver hook does not provide polyfill to give you control, but it's recommended. You can add it by re-exporting the hook like this:
// useResizeObserver.ts
import { ResizeObserver } from '@juggle/resize-observer'
import { useResizeObserver } from 'usehooks-ts'
if (!window.ResizeObserver) {
window.ResizeObserver = ResizeObserver
}
export { useResizeObserver }
Usage
import { useRef, useState } from 'react'
import { useDebounceCallback } from '../useDebounceCallback'
import { useResizeObserver } from './useResizeObserver'
type Size = {
width?: number
height?: number
}
export default function Component() {
const ref = useRef<HTMLDivElement>(null)
const { width = 0, height = 0 } = useResizeObserver({
ref,
box: 'border-box',
})
return (
<div ref={ref} style={{ border: '1px solid palevioletred', width: '100%' }}>
{width} x {height}
</div>
)
}
export function WithDebounce() {
const ref = useRef<HTMLDivElement>(null)
const [{ width, height }, setSize] = useState<Size>({
width: undefined,
height: undefined,
})
const onResize = useDebounceCallback(setSize, 200)
useResizeObserver({
ref,
onResize,
})
return (
<div
ref={ref}
style={{
border: '1px solid palevioletred',
width: '100%',
resize: 'both',
overflow: 'auto',
maxWidth: '100%',
}}
>
debounced: {width} x {height}
</div>
)
}
API
function useResizeObserver(options: UseResizeObserverOptions<T>): Size
Custom hook that observes the size of an element using the ResizeObserver API.
Parameters
| Name | Type | Default value | Description |
|---|---|---|---|
options | UseResizeObserverOptions<T> | - | The options for the ResizeObserver. |
Returns
- The size of the observed element.
Type declaration
Size
The size of the observed element.
| Name | Type | Description |
|---|---|---|
height | `number | undefined` |
width | `number | undefined` |
UseResizeObserverOptions
The options for the ResizeObserver.
| Name | Type | Description |
|---|---|---|
box | `"border-box" | "content-box" |
onResize | (size: Size) => void | When using onResize, the hook doesn't re-render on element size changes; it delegates handling to the provided callback. |
ref | `RefObject<T | null>` |
Hook
import { useRef, useState } from 'react'
import type { RefObject } from 'react'
import { useIsMounted } from '../useIsMounted'
import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect'
/** The size of the observed element. */
export type Size = {
/** The width of the observed element. */
width: number | undefined
/** The height of the observed element. */
height: number | undefined
}
/** The options for the ResizeObserver. */
export type UseResizeObserverOptions<T extends HTMLElement = HTMLElement> = {
/** The ref of the element to observe. */
ref: RefObject<T | null>
/**
* When using `onResize`, the hook doesn't re-render on element size changes; it delegates handling to the provided callback.
* @default undefined
*/
onResize?: (size: Size) => void
/**
* The box model to use for the ResizeObserver.
* @default 'content-box'
*/
box?: 'border-box' | 'content-box' | 'device-pixel-content-box'
}
const initialSize: Size = {
width: undefined,
height: undefined,
}
/**
* Custom hook that observes the size of an element using the [`ResizeObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).
* @template T - The type of the element to observe.
* @param {UseResizeObserverOptions<T>} options - The options for the ResizeObserver.
* @returns {Size} - The size of the observed element.
* @public
* @see [Documentation](https://usehooks-ts.com/react-hook/use-resize-observer)
* @example
* ```tsx
* const myRef = useRef(null);
* const { width = 0, height = 0 } = useResizeObserver({
* ref: myRef,
* box: 'content-box',
* });
*
* <div ref={myRef}>Hello, world!</div>
* ```
*/
export function useResizeObserver<T extends HTMLElement = HTMLElement>(
options: UseResizeObserverOptions<T>,
): Size {
const { ref, box = 'content-box' } = options
const [{ width, height }, setSize] = useState<Size>(initialSize)
const isMounted = useIsMounted()
const previousSize = useRef<Size>({ ...initialSize })
const onResize = useRef<((size: Size) => void) | undefined>(undefined)
onResize.current = options.onResize
const observedElement = ref.current
useIsomorphicLayoutEffect(() => {
if (typeof window === 'undefined' || !('ResizeObserver' in window)) {
return
}
let cancelled = false
let observer: ResizeObserver | null = null
let rafId = 0
let pollAttempts = 0
const teardown = () => {
observer?.disconnect()
observer = null
}
const bind = (element: T) => {
teardown()
observer = new ResizeObserver(([entry]) => {
if (!entry) return
const boxProp =
box === 'border-box'
? 'borderBoxSize'
: box === 'device-pixel-content-box'
? 'devicePixelContentBoxSize'
: 'contentBoxSize'
const newWidth = extractSize(entry, boxProp, 'inlineSize')
const newHeight = extractSize(entry, boxProp, 'blockSize')
const hasChanged =
previousSize.current.width !== newWidth || previousSize.current.height !== newHeight
if (hasChanged) {
const newSize: Size = { width: newWidth, height: newHeight }
previousSize.current.width = newWidth
previousSize.current.height = newHeight
if (onResize.current) {
onResize.current(newSize)
} else if (isMounted()) {
setSize(newSize)
}
}
})
observer.observe(element, { box })
}
const run = () => {
if (cancelled) {
return
}
const element = ref.current
if (!element) {
teardown()
previousSize.current = { ...initialSize }
if (!onResize.current) {
setSize(initialSize)
}
if (pollAttempts++ < 32) {
rafId = requestAnimationFrame(run)
}
return
}
pollAttempts = 0
bind(element)
}
run()
return () => {
cancelled = true
cancelAnimationFrame(rafId)
teardown()
}
}, [box, observedElement, isMounted, ref])
return { width, height }
}
/** @private */
type BoxSizesKey = keyof Pick<
ResizeObserverEntry,
'borderBoxSize' | 'contentBoxSize' | 'devicePixelContentBoxSize'
>
function extractSize(
entry: ResizeObserverEntry,
box: BoxSizesKey,
sizeType: keyof ResizeObserverSize,
): number | undefined {
if (!entry[box]) {
if (box === 'contentBoxSize') {
return entry.contentRect[sizeType === 'inlineSize' ? 'width' : 'height']
}
return undefined
}
return Array.isArray(entry[box])
? entry[box][0][sizeType]
: // @ts-ignore Support Firefox's non-standard behavior
(entry[box][sizeType] as number)
}