How to use Resize Observer with React
Tags: javascript, react, webdev, beginners
Published: 2022-07-09 20:40:46

In the first part of the **Web APIs series** [Quick guide to Resize Observer](https://dev.to/murashow/quick-guide-to-resize-observer-gam) we've learnt what the Resize Observer API is and how to use it with vanilla JavaScript. But what to do when it comes to using with React?
Today we are going to see how to do it quick & easy and will create a custom hook, which you can use in your projects.

## The API
Let's repeat what we know already:
- **ResizeObserver** is used to observe changes to Element's size,
- to **create** our own observer instance we call the `ResizeObserver` constructor passing the callback function that will be fired every time, when the size changes:
```typescript
const myObserver = new ResizeObserver(
  (entries: ResizeObserverEntry[], observer: ResizeObserver) => {
    for (let entry of entries) {
      // Do something with an entry (see in next section)
    }
});
```
- to **start** / **stop** watching the Element's size we shall invoke `observe` / `unobserve` instance's methods:
```typescript
const myElement = document.getElementById('my-element');
myObserver.observe(myElement); // Start watching
myObserver.unobserve(myElement); // Stop watching
```
- each observed **entry** contains entry's element sizes info:
```typescript
interface ResizeObserverEntry {
  readonly target: Element;
  readonly borderBoxSize: ReadonlyArray<ResizeObserverSize>;
  readonly contentBoxSize: ReadonlyArray<ResizeObserverSize>;
  readonly devicePixelContentBoxSize: ReadonlyArray<ResizeObserverSize>;
  readonly contentRect: DOMRectReadOnly; // May be deprecated, don't use it!
}

interface ResizeObserverSize {
    readonly blockSize: number;
    readonly inlineSize: number;
}
```

## The Task
Next we want to use our knowledge to get the sizes in any React app. There is no better solution than creating a React Hook, which could be used across all project in any component.
So let's try to define what exactly we want from the hook:

1. It should be _universal_ and its usage must be as _simple_ as `const size = giveMeMySize()`;
2. As you've probably seen (I hope 😄) in the previous section, one `ResizeObserver` instance is able to handle any amount of elements. If we want to keep our app performant the hook should use only _single observer instance_ inside;
3. In order make the hook handy to use it should _deal with mutations_ itself and automatically _stop observing on unmount_;
4. Keep in mind that although the `ResizeObserver API` already has broad support, it is still in Editor’s Draft and isn't supported by all browsers. It's better to provide a _fallback_ for it.

## The Solution
Our requirements look good and pretty strict, uh? But don't worry, we can deal with all of them using the beautiful and very easy-to-use `useResizeObserver` hook from the beautiful [react-hook](https://github.com/jaredLunde/react-hook) library by _Jared Lunde_. According to it's [documentation](https://github.com/jaredLunde/react-hook/tree/master/packages/resize-observer#readme) and my tests and usage as well it meets all our requirements:

> - Uses a single `ResizeObserver` for tracking all elements used by the hooks. [This approach is astoundingly more performant](https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/z6ienONUb5A/F5-VcUZtBAAJ) than using a `ResizeObserver` per element which most hook implementations do,
> - Uses [@juggle/resize-observer](https://github.com/juggle/resize-observer) as a ponyfill when `ResizeObserver` isn't supported by the current browser,
> - automatically unobserves the target element when the hook unmounts,
> - you don't have to wrap your callback in `useCallback()` because any mutations are handled by the hook.

Feels promising, why don't we use it right now?

## The Hook
We are ready to create our hook finally. First of all, install the  `useResizeObserver` hook:
```
npm install @react-hook/resize-observer
// Or
yarn install @react-hook/resize-observer
```
Then let's define how our hook will look like:
```typescript
// useElementSize.ts
import { MutableRefObject, useLayoutEffect, useRef, useState } from 'react';
import useResizeObserver from '@react-hook/resize-observer';

interface Size {
  width: number;
  height: number;
}

export default function useElementSize<T extends HTMLElement = HTMLDivElement>(): [MutableRefObject<T | null>, Size] {
  const target = useRef<T | null>(null);
  const [size, setSize] = useState<Size>({
    width: 0,
    height: 0,
  });

  return [target, size]
}
```
As you see, we've created the `useElementSize` function, which we can provide a generic type of our Element. It creates the `target` reference to connect to the React element inside a component and the `size` for the current Element's size state, which implements the `Size` interface.

Keep in mind that while Element is resizing, its dimensions can be (and usually are) decimal numbers. We can round them of course:
```typescript
const setRoundedSize = ({ width, height }: Size) => {
  setSize({ width: Math.round(width), height: Math.round(height) });
};
```

Next, we need to set the initial `size` of the Element. This is where the React `useLayoutEffect` hook fits perfectly. It fires before the browser paint, allowing us to get the Element's dimensions using its [getBoundingClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) method:
```typescript
useLayoutEffect(() => {
    target.current && setRoundedSize(target.current.getBoundingClientRect())
}, [target]);
```

And last but not least, let's put some magic (not) there with the help of the `useResizeObserver` hook, that will trigger the `size` update each time the target's size changes:
```typescript
useResizeObserver(target, entry => {
  const { inlineSize: width, blockSize: height } = entry.contentBoxSize[0];
  setRoundedSize({ width, height });
});
```

## And the usage 🤠
Let's try to repeat the exercise from the first part [Quick guide to Resize Observer](https://dev.to/murashow/quick-guide-to-resize-observer-gam) of the series, let me remind you the task: 

> Say we have a box of strawberries and getting them bigger makes us really happy (and vice versa).

I won't go into detailed explanations, because as you remember, our goal was to create a hook that is very easy to use. You can check the code and how it works all together below.
**How do you think it worked out?**

{% embed https://codesandbox.io/embed/how-to-use-resize-observer-with-react-hu184x?hidenavigation=1&view=preview %}

---
Hope you enjoyed this guide, stay tuned for more.