useResizeObserver
Custom hook that observes the size of an element using the ResizeObserver API
.
Usage
import { useRef, useState } from 'react'
import { useDebounceCallback, useResizeObserver } from 'usehooks-ts'
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
▸ useResizeObserver<T
>(options
): Size
Custom hook that observes the size of an element using the ResizeObserver API
.
Type parameters
Name | Type | Description |
---|---|---|
T | extends HTMLElement = HTMLElement | The type of the element to observe. |
Parameters
Name | Type | Description |
---|---|---|
options | UseResizeObserverOptions <T > | The options for the ResizeObserver. |
Returns
- The size of the observed element.
Type aliases
Ƭ Size: Object
The size of the observed element.
Type declaration
Name | Type | Description |
---|---|---|
height | number | undefined | The height of the observed element. |
width | number | undefined | The width of the observed element. |
Ƭ UseResizeObserverOptions<T
>: Object
The options for the ResizeObserver.
Type parameters
Name | Type |
---|---|
T | extends HTMLElement = HTMLElement |
Type declaration
Name | Type | Description |
---|---|---|
box? | "border-box" | "content-box" | "device-pixel-content-box" | The box model to use for the ResizeObserver. Default ts '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. Default ts undefined |
ref | RefObject <T > | The ref of the element to observe. |
Hook
import { useEffect, useRef, useState } from 'react'
import type { RefObject } from 'react'
import { useIsMounted } from 'usehooks-ts'
type Size = {
width: number | undefined
height: number | undefined
}
type UseResizeObserverOptions<T extends HTMLElement = HTMLElement> = {
ref: RefObject<T>
onResize?: (size: Size) => void
box?: 'border-box' | 'content-box' | 'device-pixel-content-box'
}
const initialSize: Size = {
width: undefined,
height: undefined,
}
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
useEffect(() => {
if (!ref.current) return
if (typeof window === 'undefined' || !('ResizeObserver' in window)) return
const observer = new ResizeObserver(([entry]) => {
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(ref.current, { box })
return () => {
observer.disconnect()
}
}, [box, ref, isMounted])
return { width, height }
}
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)
}