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:
- Creating an instance of
ResizeObserver, passing it a callback executed each time one of the observed elements’ size has changed; - 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 (<divstyle={{// Initial sizewidth: 150, height: 100,// Makes the element resizeableresize: 'both', overflow: 'auto',// So it’s easier to resizeborder: '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