DEV Community

Vladimir Ivanenko
Vladimir Ivanenko

Posted on

Building responsive widgets

Prologue

A responsive interface in the era of mobile devices is more than just a good feature to have. It's one of the vital characteristics of an app or a website in its way to guarantee a nice user experience. And we got a powerful tool bringing it to life — Media Queries.

Though media queries provide us with a quite comprehensive set of capabilities they still have their limitations. One of them concerned me while I've been building widgets is that I can adapt the layout based only on the viewport but not a particular element's size.

There is an upcoming feature called Container Queries which once supported by browsers will give us more flexibility. As for now, the specification is in a working draft and we can't expect it to be widely available very soon.

If you're eager to try it now check out what browsers added experimental support for it already.

Let's get to the point.

"The point"

To be on the same page, let's define a problem first.

Why we may need to know an element's size?
Well, because we may want to make content inside adaptable for its different size variants. And in the case of widgets, we have no idea of a container's dimensions within which the widget is placed. And as we know, media queries won't help much because they work with the viewport.

The minimal solution is obvious and straightforward — add a prop to allow developers who use your widget component to decide what layout to apply. It's perfect for components libraries because it doesn't make assumptions itself on which layout variant is best but gives a developer a right to choose.

But there are certain cases when making a widget responsive right out of the box could be beneficial. Especially when it takes to visual builders or another tool for non-developers.

It's time to write some code.
React refs can give us access to a DOM element, therefore we can adjust a layout based on its width.

import { useState, useCallback } from "react";

function Widget() {
  const [layoutVariant, setLayoutVariant] = useState("default");
  const updateLayoutVariant = useCallback((element: Element | null) => {
    if (element) {
      const rect = element.getBoundingClientRect();
      setLayoutVariant(rect.width < 300 ? "narrow" : "default");
    }
  }, []);

  return <Layout variant={layoutVariant} ref={updateLayoutVariant} />;
}
Enter fullscreen mode Exit fullscreen mode

And it works well enough.
If it meets your requirements, awesome, you can stick with it!

In the code above we set the layout variant only once when the component is mounted. If we expect the container's width changes during the component lifetime because of subsequent render or a window resize we have to make some improvements to our solution.

import {
  useState,
  useRef,
  useCallback,
  useEffect,
  useLayoutEffect
} from "react";

function Widget() {
  const [layoutVariant, setLayoutVariant] = useState("default");
  const containerRef = useRef<HTMLDivElement | null>(null);
  const updateLayoutVariant = useCallback(() => {
    if (containerRef.current) {
      const rect = containerRef.current.getBoundingClientRect();
      setLayoutVariant(rect.width < 300 ? "narrow" : "default");
    }
  }, []);

  useEffect(() => {
    window.addEventListener("resize", updateLayoutVariant);
    return () => {
      window.removeEventListener("resize", updateLayoutVariant);
    };
  }, [updateLayoutVariant]);

  useLayoutEffect(() => {
    updateLayoutVariant();
  });

  return (
    <Layout variant={layoutVariant} ref={containerRef} />
  );
}
Enter fullscreen mode Exit fullscreen mode

Now we check out whether our widget's layout needs to be updated, however, the code became a bit more complicated.

There are open-source utilities that could be found helpful:
react-use-measure, react-use-rect.

Thank you for reading the article! Hope you could put it to good use.

Discussion (0)