useEffect.dev
← Back to lessons

Lesson 12. Custom hooks to use the browser’s APIs 3. Get an element’s size

Another thing you might want to do is adapt your component's behavior depending on some element size. What would be cool is having a hook returning me the current width and height of any element I want in realtime. Let’s see how we can create such a hook.

First, let’s put React aside for a minute and see how to get the size of a DOM element using plain JavaScript. Modern browsers offer an object ResizeObserver that we can use for that. Its API is not the easiest to apprehend at first sight; for our use case, it consists in:

  1. Creating an instance of ResizeObserver, passing it a callback executed each time one of the observed elements’ size has changed;
  2. Subscribe to observe each element we want to.

Here is an example displaying in the console the width and height of an element each time it is modified:

const element = document.querySelector('#myElement')
// 1.
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
if (entry.contentRect) {
console.log(entry.contentRect.width, entry.contentRect.height)
}
}
})
// 2.
resizeObserver.observe(element)

Note that we loop through several entries in the callback given to RedizeObserver; this is because an observer can observe several elements, although we will only observe one here.

Let’s come back to React: to know the size of a DOM element, we need first to get this element. We will need to use a ref, via the useRef hook. We saw how refs were useful in a previous lesson when dealing with async code; here is another common use case.

By creating a ref with useRef and passing it as the ref prop of any HTML element rendered in your component, you can access the DOM element itself via yourRef.current:

const inputRef = useRef()
useEffect(() => {
console.log(inputRef.current.value)
// logs “Hello!”
}, [inputRef])
return <input ref={inputRef} defaultValue="Hello" />

Here we need this ref to observe it via our ResizeObserver, so we will pass it as a parameter to our custom hook. Here is how we expect to use our hook; let’s name it useElementSize:

const Comp = () => {
const divRef = useRef()
const [width, height] = useElementSize(divRef)
return (
<div
style={{
// Initial size
width: 150, height: 100,
// Makes the element resizeable
resize: 'both', overflow: 'auto',
// So it’s easier to resize
border: '1px solid #191a21',
}}
ref={divRef}
>
{width}x{height}
</div>
)
}

As you can see, we want our hook to return the width and height of the element pointed by the ref, and of course, we want these values to be updated when the user resized the element.

So our hook useElementSize has to keep the current element’s width and height in a local state, and returns them:

const useElementSize = (elementRef) => {
const [width, setWidth] = useState(undefined)
const [height, setHeight] = useState(undefined)
// ...
return [width, height]
}

The last missing piece is to create the ResizeObserver to update these local state values when the element is resized:

useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
if (entry.contentRect) {
setWidth(entry.contentRect.width)
setHeight(entry.contentRect.height)
}
}
})
resizeObserver.observe(elementRef.current)
// Let’s disconnect the observer on unmount:
return () => { resizeObserver.disconnect() }
}, [elementRef])

Notice that we subscribe to the observer in a useEffect and that we disconnect the observer when the component is unmounted.

Here is the complete code for the example:

Result