DEV Community

Cover image for The power of custom hooks in React (responsive design example)
Matan Levi
Matan Levi

Posted on

The power of custom hooks in React (responsive design example)

Generally, custom hook is a great pattern in order to handle modularity and composition in your app. you can write a custom hook for almost everything!

BTW, if you're interested in custom hooks, I assume you are familiar with React hooks concept. if not, no worries, you can read about it here.

Something that worth to mention here (taken from React docs):

Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don’t work inside classes — they let you use React without classes. (We don’t recommend rewriting your existing components overnight but you can start using Hooks in the new ones if you’d like.)

"Captain hook"

Let's take responsive window handling as an example..

The most common approach for responsive design is CSS media queries, but in some cases, we will want to handle it via JavaScript (conditionally render components, execute some logic when window size is changing to some point, etc.)

In the example below you will see how we can use React hooks for that purpose + compose code and reuse/share it across an app(s).

Some declarations

Our custom hook is called useResponsiveWindow and gets sizes as an optional object.

Hook name convention is a use prefix followed by a story telling, camel cased name (like useState, useRef, useContext, etc.)

Most of the apps use these common sizes which declared as a default using DEFAULT_SIZES, but feel free to change or pass your own to the hook.

DESKTOP_MIN size is also a standard minimum resolution for a desktop view (Again, not a taboo..). we will use it later.

First, we will want to save state for first, on load, width & height using useState.

const DEFAULT_SIZES = {
  small: [1366, 768],
  medium: [1400, 900],
  large: [1920, 1080],
  mobile: [360, 640]
};

export enum ResolutionState {
  XS = "Extra Small",
  SMALL = "Small",
  MEDIUM = "Medium",
  LARGE = "Large"
}

const DESKTOP_MIN = [1280, 720];

const useResponsiveWindow = (sizes = DEFAULT_SIZES) => {
  const [width, setWidth] = useState(window.innerWidth);
  const [height, setHeight] = useState(window.innerHeight);
  const resizeTimer = useRef(null);

.........
Enter fullscreen mode Exit fullscreen mode

Track window size and store it

Adding resize event listener (remove it on unmount) and execute handleWindowResize which will save the new values.

.....

  const handleWindowResize = useCallback((e) => {
    clearTimeout(resizeTimer.current);
    resizeTimer.current = setTimeout(() => {
      setWidth(e.target.innerWidth);
      setHeight(e.target.innerHeight);

    }, 200);

  }, [setWidth, setHeight, resizeTimer]);

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

.....
Enter fullscreen mode Exit fullscreen mode

Useful insights

Now that we have width, height and resolution thresholds, we get some insights that we can use in our application.

.....

  const resolutionState = useCallback((type) => {
    const index = type === 'width' ? 0 : 1;
    const value = type === 'width' ? width : height;
    if(value >= sizes?.small[index] && value < sizes?.medium[index]) {
      return ResolutionState.SMALL;
    } else if(value >= sizes?.medium[index] && value < sizes?.large[index]) {
      return ResolutionState.MEDIUM;
    } else if(value >= sizes?.large[index]) {
      return ResolutionState.LARGE;
    } else {
      return ResolutionState.XS;
    }
  }, [width, height]);

  const widthState = resolutionState('width');
  const heightState = resolutionState('height');

  const isMobile = useMemo(() => sizes?.mobile && width <= sizes?.mobile[0] && height <= sizes?.mobile[1], [width, height]);

  const isDesktop = useMemo(() => width >= DESKTOP_MIN[0] && height >= DESKTOP_MIN[1], [width, height]);

.....
Enter fullscreen mode Exit fullscreen mode

Consuming the hook

const SomeComponent= () => {
  const {
    width,
    height,
    isMobile,
    isDesktop,
    widthState,
    heightState
  } = useResponsiveWindow();

  useEffect(() => {
    console.log(`Width state now is: ${widthState}`);
    // do something here...
  }, [widthState]);

  return (
    <div>
      <p>{`${width} (${widthState}) x ${height} (${heightState})`}</p>
      {isMobile && <div>Mobile View</div>}
      {isDesktop && <div>Desktop View</div>}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

You can view an example here and the source code here:

Conclusion

There are many libraries providing many custom hooks as a solution, and probably your app has a lot of those, but try to find some that you can implement yourself, it will:

  • saves you bundle size.
  • gives you a full control of your code.
  • extend your code skills outside app borders, into the infra zone.

About this hook - it's not perfect, and probably able to offer much more insights and useful data, according to your needs.

Suggestions and thought are more than welcome :)

Image by Dean Moriarty from Pixabay

Top comments (9)

Collapse
 
httpjunkie profile image
Eric Bishard

I'm a big fan of the useMediaPredicate hook from the react-media-hook, but this is an interesting article on building your own custom hook, but I do like that the react-media-hook can be used with any media query. Good job

Collapse
 
mlevi1806 profile image
Matan Levi • Edited

Thanks for the feedback.

I agree that there are libraries providing a large set of features (like react-responsive) and it's nice that you can use them on-the-go.
On the other hand, it is nice to figure out what you need and add it to your own hook + the pros I've mentioned above

Collapse
 
d0m00re profile image
d0m00re

I use this kind of approach: (styled-components-approach)
const Flex = styled.div
js
@media ${(props) => props.theme.device.mobileL} {}
@media ${(props) => props.theme.device.laptop}{}
@media ${(props) => props.theme.device.desktop} {};

I combine that with styled components and style theme swiching (maintain multiple type of responsive with styled theme).
;

Collapse
 
theqwertypusher profile image
Jason Victor

This is interesting. I just started using styled-components and now figuring out how to handle responsiveness. Can you think of any drawbacks to your approach?

Collapse
 
mutebg profile image
Stoyan Delev

Congrats for the article, creating custom hooks is really powerful.
The example is not the perfect tho, you can replace window event listener with window.matchMedia which is the API created for that purpose.

Collapse
 
mlevi1806 profile image
Matan Levi

Thank you :)

The article main purpose is to show how a simple custom hook can be very useful, yet your insight is very interesting, I’ll look into that.
I was informed by a colleague that ResizeObserver is a good option as well.

Collapse
 
d0m00re profile image
d0m00re

Thank you for this approach :)

Collapse
 
mlevi1806 profile image
Matan Levi

You welcome :)

Collapse
 
walfredocarneiro profile image
Walfredo Carneiro

Thats's great!