Watching the size of a React element

Articles|Håkon Underbakke | almost 3 years ago

I’ve recently come upon the task to replace a React accordion component from a UI library. This turned out to be an interesting challenge that has taught me a few things about MutationObservers and layout effects.

The problem

If you’re going to animate between states, you’re going to need hard values. There is a trick to work around this: animating the max-height to something taller than ever needed – but then you’d somewhat lose control over the animation-duration and easing.

The solution

So we’re going to need the height of the content. Fair enough, just slap in a useEffect and ref and check the height of the element, right? ❌

What if the user resizes the browser-window and the content shifts around and suddenly it’s three lines instead of two? Ok fair enough, let’s slap on a useLayoutEffect with a window resize listener. Now we’ve got to be safe, right? ❌

If the content of the accordion is dynamic, and shifts height after user interaction – then we need a way to handle that as well. This is where our MutationObserver comes in. Setting up one is quite easy:

const [mutationObserver, setMutationObserver] = useState(null);
const checkForHeight = () => {/* ... */};
const setupMutationObserver = () => {
    if (!mutationObserver && ref.current) {
      const observer = new MutationObserver(checkForHeight);
      observer.observe(ref.current, {
        attributes: true,
        attributeOldValue: true,
        childList: true,
        characterData: true,
        characterDataOldValue: true,
        subtree: true
      });
      setMutationObserver(observer);
    }
};
useEffect(() => {
  setupMutationObserver();
  return () => {
    if (mutationObserver){
       mutationObserver.disconnect();
    }
  }
}, []);

You probably won’t need all of those options, you can read more about them here.

Abstracting this into a hook

You might not want to have all of the above boilerplate, coupled with resize listeners and more for every component that you need to watch the height/width of. Because of this, I’ve abstracted everything into an easy to use hook.

Note: If you are running a live-server or any type of live-reload while developing and this method stops working – it’s most likely because the while reloading your code it might run the cleanup function and disconnect the MutationObserver. I’ve yet to resolve this bug, but it does not occur in production.