Article thumbnail

🔰 Element size measurement with useElementSize hook

7m
frontend
ui
hooks

Intro

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.

Loading

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

Before we start we need to choose a native function that allows us to read height or width of an element. JavaScript contains getBoundingClientReact function, which returns above mentioned and much more!

Loading

Loading

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.

Loading

API design phase

We'll use our hook in two ways:

  1. with document.body element,
  2. with any other HTML node - like div, span, ...etc.

Loading

Before we start the implementation, let's add some type definitions for our hook in a separate file - defs.ts:

Loading

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.

Loading

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.

Implementation guide

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.

Loading

Now, just after the component mount, we need to assign the initial state and detect the current size of the element.

Loading

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.

Loading

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.

Loading

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.

Loading

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 demo

The final result

Repository to play with. In addition check the full implementation below if you are lazy like me:

Loading

Loading

Loading

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.

I create content regularly!

I hope you found my post interesting. If so, maybe you'll take a peek at my LinkedIn, where I publish posts daily.

Comments

Add your honest opinion about this article and help us improve the content.

created: 20-03-2023
updated: 20-03-2023