DEV Community

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

Posted on

`useWindowSize` React Hook To Handle Responsiveness In JavaScript

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 creating a Custom React Hook. It will determine the screen size. And, we will be able to use the screen sizes just like we do in CSS media queries to update the UI or make the logic run a specific way.

The code is dependent on the window object of the browser. The solution won't work in case of server-side rendering where the window object won't exist.

We will name the custom hook useWindowSize. We will have a state variable called windowSize which will be exported to be used by React Components.

import { useState, useEffect } from "react";

function useWindowSize() {
  const [windowSize, setWindowSize] = useState(undefined);

  ...
  //code to determine the screen size will go here

  ...

  //expose windowSize variable to be used by Components
  // to make responsiveness related chanegs
  return windowSize;
}

export default useWindowSize;

Now to determine the screen size, we will first check if the client is a browser, by checking if we have access to window object. If we have, we can get the width of the screen using window.innerWidth and assign into the state variable as default value.

import { useState, useEffect } from "react";

function useWindowSize() {
  //👇
  const isWindowClient = typeof window === "object";

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

  return windowSize;
}

export default useWindowSize;

You can use this hook in the component as below,

import React from "react";
import useWindowSize from "./useWindowSize";

export default function App() {
  const windowSize = useWindowSize();
  return (
    <div>
      <h1>The screen width is: </h1>
      <span style={{ fontSize: "30px" }}>{windowSize}</span>
    </div>
  );
}

static width gif

However, on resize of the window, this hook won't inform us about the change in size(as shown above gif). To achieve it, we will have to implement window's on resize listener. We will use it in useEffect so that we won't register the listener each time it renders and we make sure that it gets unregistered when it needs to.

import { useState, useEffect } from "react";

function useWindowSize() {
  const isWindowClient = typeof window === "object";

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

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

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

      //un-register the listener
      return () => window.removeEventListener("resize", setSize);
    }
  }, [isWindowClient, setWindowSize]);
  //☝️

  return windowSize;
}

export default useWindowSize;

dynamic width gif

Now if we want to have breakpoints instead of this absolute widths, we can convert the sizes into specific breakpoints (let say sm, md, lg, xlg) using a simple util function.

import { useState, useEffect } from "react";

//👇
//a Util function that will conver the absolute width into breakpoints
function getBreakPoint(windowWidth) {
  if (windowWidth) {
    if (windowWidth < 480) {
      return "sm";
    } else if (windowWidth < 1024) {
      return "md";
    } else if (windowWidth < 1200) {
      return "lg";
    } else {
      return "xlg";
    }
  } else {
    return undefined;
  }
}
//☝️

function useWindowSize() {
  const isWindowClient = typeof window === "object";

  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;

with breakpoint gif

The codesandbox of the final code is below

All the best for making your users happy with all size of devices! 😄

Cover Photo by Hal Gatewood on Unsplash

Top comments (12)

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.