Discover useElementSize hook for precise HTML and window size measurement. Master techniques, tips, and best practices for enhanced web development skills.
Why do we want to measure element size?
Under the following Dream stack for React dev repository, you can find the full implementation.
Sometimes we want to measure the width, height, and other properties when the user changes the viewport - imagine the browser is resized. In GIF below, we need this feature to change from preview to code editor automatically after window resize.
The use case in real app
Doing it only with @media in CSS will provide unsync between the visible UI and the internal state of the feature. We want to give the user possibility to change view, and we want to automatically set it when the viewport has a dedicated size. So, to achieve it we need to know the size of window or any other HTML node like div.
That's why creating a reusable hook may be a good idea for such cases. In the following lesson, we'll create useElementSize hook, and we will discuss implementation details with possible use cases.
The getBoundingClientReact function
The ResizeObserver API
Ok, we know how to read metadata, but how to listen to resize events? We may use ResizeObserver. It allows us to specify the target and listen to situations when the size of any element changes. It returns the same object as the getBoundingClientRect function.
API design phase
We'll use our hook in two ways:
- with document.body element,
- with any other HTML node - like div, span, ...etc.
Before we start the implementation, let's add some type definitions for our hook in a separate file - defs.ts:
You may asking yourself: "Why have we used unions to define a state shape?". So, if we are not able to detect the size, there is no point for allocating in memory 2 additional properties of an object - height and width. In addition, it will make easier for us to check whether the size has been already checked or not.
The developer experience will be much better, less memory allocated and we'll be protected from making a common mistake - reading the value which is not ready to be used.
If you're curious why it's worth to write code in this way, feel free to read the following 🌟 Concerns about separating types from implementation article.
At the beginning let's import previously defined types into a new file - use-element-size.ts and let's add the basic boilerplate that we need.
Now, just after the component mount, we need to assign the initial state and detect the current size of the element.
We've used weird useIsomorphicLayoutEffect. We need to read the data and be sure that the browser performed paint - that's why we need to use useLayoutEffect hook for that.
However, if we use useLayoutEffect on the server side - NextJS, we'll see an error. To avoid that, we need a simple abstraction that when the code will be used on the server side, the useEffect will be used, in another case the useLayoutEffect will be used.
If you're interested in this topic, dive for information to 🔰 Removing server warnings for useLayoutEffect with custom hook lesson.
Okay, now when you use our hook on any component, the initial state will be assigned and you'll be able to use it inside your JSX. Unfortunately, still we need a resizing check and update when it happens. To achieve this we'll use ResizeObserver API mentioned before.
Now, when you open the browser and resize the window, the state will be updated. Still, there is a small problem - the event is called too often. We should have the option to reduce the amount of calls. For example, we may use a typical debounce mechanism for that. So, when user will resize the window, we'll wait and then we'll take only last emitted event.
You may implement this mechanism on your own, use it from Lodash, or another library. I'll use in this example the RxJs operators.
Now, when we try to resize the default delay will be used to debounce events. So, the callbacks will trigger rerenders in rare cases.
The final result
Repository to play with. In addition check the full implementation below if you are lazy like me:
The conclusions and thoughts
We've created a really nice hook that is able to listen for body and any other HTML node resize events. The hook prevents us from making popular mistakes when working with not ready state and guides us by hand on how to use the attached metadata.
- 1. Rendering
Creating portals with custom usePortal hook
We will change the screens with useStepper hook
Manage components appearance with useToggle hook
Removing server warnings for useLayoutEffect with custom hook
- 2. Forms
- 3. Events
Read the scroll metadata and direction with useScroll hook
Using clipboard with useClipboard hook
Detect outside click with the useClickOutside hook
Deep dive into useIntersectionObserver hook
Element size measurement with useElementSize hook
- 4. Guards
- 5. Interactions