DEV Community

Cover image for `useWindowSize` React Hook To Handle Responsiveness In JavaScript

`useWindowSize` React Hook To Handle Responsiveness In JavaScript

Sanket Patel on March 05, 2020

It is sometimes helpful to know the responsive breakpoints in JavaScript to tweak and run the logic depending on the screen size. We will be creat...
Collapse
 
lexlohr profile image
Alex Lohr

I would prefer making this more general by taking a configuration object with the desired sizes:

interface Breakpoints {
    [key: string]: number;
}

const isWindowClient = typeof window === "object";

const useWindowSize = (breakpoints: Breakpoints) => {
  const getBreakPoint = useMemo(
    (width: number) => Object.values(breakpoints)
      .find(([_, size]) => width <= size)
      .map(([id]) => id), 
    [breakpoints]
  );

  const [windowSize, setWindowSize] = useState(
    isWindowClient
      ? getBreakPoint(window.innerWidth)
      : undefined
  );

  useEffect(() => {
    //a handler which will be called on change of the screen resize
    function setSize() {
      setWindowSize(getBreakPoint(window.innerWidth));
    }

    if (isWindowClient) {
      //register the window resize listener
      window.addEventListener("resize", setSize);

      //unregister the listerner on destroy of the hook
      return () => window.removeEventListener("resize", setSize);
    }
  }, [isWindowClient, setWindowSize]);

  return windowSize;
}

export default useWindowSize;
Collapse
 
3sanket3 profile image
Sanket Patel

Cool! Additionally, the way you used Object.values + find + map, refreshed usage of few concepts which I knew but rarely put in practice. Thanks.

Collapse
 
daxsoft profile image
Michael Willian Santos

My aproach is this way: (Someday I'll publish my hooks xD)

import { useState, useEffect, useMemo, useCallback } from 'react'

function getSize(screenSize, config, aspect) {
    if (typeof window === 'undefined') {
        return config
    }

    let { innerWidth, innerHeight, outerWidth, outerHeight } = window

    innerWidth *= aspect[0] || 1
    innerHeight *= aspect[1] || 1

    const orientation = innerWidth > innerHeight ? 'landscape' : 'portrait'
    const ratio = +parseFloat(innerHeight / innerWidth).toFixed(2)

    let difference = 0

    if (screenSize && screenSize.ratio) {
        difference = (screenSize.ratio - ratio) * 100
        if (difference < 0.0) difference *= -1
    }

    return {
        innerWidth,
        innerHeight,
        outerWidth,
        outerHeight,
        ratio,
        difference,
        orientation,
        previous: screenSize,
        aspect,
    }
}

export default function useScreenSize(
    config = {
        innerWidth: 1360,
        innerHeight: 640,
        outerWidth: 1360,
        outerHeight: 640,
        ratio: 0.47,
        orientation: 'landscape',
        difference: 1,
    },
    margin = [1, 1]
) {
    const [screenSize, setScreenSize] = useState(getSize(null, config, margin))

    const handleResize = useCallback(
        () => setScreenSize(getSize(screenSize, config, margin)),
        []
    )

    useEffect(() => {
        window.addEventListener('resize', () => {
            handleResize()
        })

        return () => {
            window.removeEventListener('resize', handleResize)
        }
    }, [])

    return useMemo(
        () => ({
            ...screenSize,
            fullHD:
                screenSize.innerWidth >= 1920 && screenSize.innerHeight >= 1080,
            hd: screenSize.innerWidth >= 1360 && screenSize.innerHeight >= 724,
        }),
        [screenSize]
    )
}

So, you can use like this:

const Page = ({ token }) => {
    const screenSize = useScreenSize({}, [0.9, 1.35])

    return (
        <>
            <Head>
                <title>Assine Aqui | Meu perfil</title>          
            </Head>

            {(screenSize.orientation === 'landscape' ? (
                    <Desktop token={token} profileData={profileData} />
                ) : (
                    <Mobile token={token} profileData={profileData} />
                ))}
        </>
    )
}
Collapse
 
3sanket3 profile image
Sanket Patel

Wow this is an comprehensive one. I think I should sit back and give time to imagine all use cases it covers.

Collapse
 
stfbauer profile image
Stefan Bauer

Hi,

I am just wondering why you use matchMedia instead of all these complex calculations? media queries in JavaScript too matchMedia

Thanks
Stefan

Collapse
 
3sanket3 profile image
Sanket Patel

First time heard about the matchMedia. Thanks for the input.

Just wondering, is there any way to react if screen resized?

Collapse
 
stfbauer profile image
Stefan Bauer

Of course, it has an event receiver for onChange.

developer.mozilla.org/en-US/docs/W...

Thread Thread
 
3sanket3 profile image
Sanket Patel • Edited

that's cool!

Collapse
 
pataco80 profile image
Pataco80

Hello,

I am new to React and have never used a custom houk. It seems to me that I understood how your code works, but I don't quite understand how to build the logic on a component in order to return inside the choice of 2 components like the navigation bar. Ex: I have a global topBar container, and I have 2 components to return, either a dropdownMenu or from the large tablet, bring up the other navigation component navMenu.

How can I implement the logic against the code you offer?

Thank you.

Collapse
 
fnaquira profile image
Favio Náquira

Thank you very much. I was reading this article and blessing you from Perú, keep going :)

Collapse
 
3sanket3 profile image
Sanket Patel

Thanks for your kind words, Favio😊

Collapse
 
ardiaankur profile image
Ankur Gupta

This does not work on page reload on mobile devices or when you go from one page to another page in mobile device. It returns infinity value in that case.