useMediaSizes() Hook

Dynamic Responsive Design Hook

The useMediaSizes() hook allows you to easily determine the size of the viewport bases on a set of breakpoints. This is particularly useful for determining the placement of components based on the size of the viewport.

useMediaSizes.tsx
import { useEffect, useMemo, useState } from "react";

export interface MediaBreakpoints { [name: string]: number; }

export interface MediaSizes { [name: string]: boolean; }

const defaultBreakpoints: MediaBreakpoints = {
    sm: 640,
    md: 768,
    lg: 1024,
    xl: 1280,
};

const breakpointsToSizes = (breakpoints: MediaBreakpoints) => Object.keys(breakpoints).reduce(
    (acc, key) => ({
        ...acc,
        [key]: window.innerWidth > breakpoints[key],
    }), {}
);

const debounce = <F extends (...args: any[]) => any>(func: F, wait: number) => {
    let timeout: string | number | NodeJS.Timeout | undefined;
    return (...args: any) => {
        if (timeout) {
            clearTimeout(timeout);
        }
        timeout = setTimeout(() => func(args), wait);
    }
}

export function useMediaSizes(customBreakpoints?: MediaBreakpoints): MediaSizes {
    const breakpoints = useMemo(() => ({
        ...defaultBreakpoints,
        ...customBreakpoints,
    }), [customBreakpoints]);

    const [sizes, setSizes] = useState<MediaSizes>(window && breakpointsToSizes(breakpoints));

    useEffect(() => {
        if (!window) { return; }

        const handleResize = () => setSizes(breakpointsToSizes(breakpoints));

        const debouncedHandleResize = debounce(handleResize, 100);
        window.addEventListener("resize", debouncedHandleResize);
        return () => window.removeEventListener("resize", debouncedHandleResize);
    }, [breakpoints]);

    return sizes;
}

Example Usage

The useMediaSizes() hook can be used to determine the placement of a certain component based on the size of the viewport. In the example below, the LayoutComponent places the Menu component in the SideBar component when the viewport is greater than 768px and in the MainContent component when the viewport is less than 768px.

LayoutComponent.tsx
import React, { useRef } from 'react';
import { useMediaSizes } from '@/hooks/useMediaSizes';
import SideBar from '@/components/SideBar';
import MainContent from '@/components/MainContent';

const LayoutComponent = () => {
    const { sm, md, ...sizes } = useMediaSizes({
        sm: 500,      // overrides the default sm breakpoint
        '2xl': 1536,  // adds a new 2xl breakpoint
    }); // Use the hook to check the media sizes.

    // md: boolean - true if the viewport is greater than 768px
    // sm: boolean - true if the viewport is greater than 500px
    // sizes['2xl']: boolean - true if the viewport is greater than 1536px

    return (
        <div>
            <SideBar> { md && <Menu /> } </SideBar>
            <MainContent> { !md && <Menu /> } </MainContent>
        </div>
    ); 
};

export default LayoutComponent;