Malaury Boudon
Malaury Boudon

Reputation: 732

How to detect the window width change?

I have different pages of content to apply, depending on the width of the window. I did it with the condition:

{window.innerWidth > 575 ? <Big component> : <Little component>}

It works well but not if you reduce the page without reload manually.

I tried to implements useEffect on it, but I don't find how to trigger it by the widow.width change. Please, could you help me ?

This is my code:

 const [windowSize, setWindowSize] = useState(window.innerWidth);

 useEffect(() => {
   setWindowSize(window.innerWidth);
 });

 return (
    ... 
      {windowSize > 575 ?  <Big component> : <Little component>}
    ...
);

Thanks in advance.

Upvotes: 2

Views: 4814

Answers (3)

RJA
RJA

Reputation: 457

Using Utsav and MDN docs for debouncing this is my custom hook I created for my application

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


interface MediaObj {
  isMobile: boolean;
  isTablet: boolean;
  isLaptop: boolean;
  isDesktop: boolean;
};

//upper limit (BP) for each device. based off Bootstrap breakpoints
const mobileBP = 768; //sm
const tabletBP = 992;  //md 
const laptopBP = 1200  //lg
const dekstopBP = 1400; //xlg

const defaultValues= {
  isMobile: window.innerWidth <= mobileBP ? true : false,
  isTablet: (window.innerWidth > mobileBP && window.innerWidth <= tabletBP) ? true : false,
  isLaptop: (window.innerWidth > tabletBP && window.innerWidth <= laptopBP) ? true : false,
  isDesktop: (window.innerWidth > laptopBP) ? true : false,
}

const useDetectResize = () => {
  const [mediaType, setMediaType] = useState<MediaObj>(defaultValues);
  const [windowDimensions, setWindowDimensions] = useState<{w:number, h:number}>({w: window.innerWidth, h: window.innerHeight});
  let timeout = false;

  //register callback so our function isn't instantiated on each re-render.
  const handleWindowResize = useCallback((timeout:any) => {
    // clear the timeout
    clearTimeout(timeout);
    // debounce getDimensions function ever N ms
    timeout = setTimeout(getDimensions, 1000);

    //store width/height and mediaState 
    function getDimensions(){
      let width = window.innerWidth;
      let height = window.innerHeight;
      setWindowDimensions(currState => currState = {w:width, h: height});
      console.log('Width Saved!: ', width);

      let mediaTypes = {
        isMobile: window.innerWidth <= mobileBP ? true : false,
        isTablet: (window.innerWidth > mobileBP && window.innerWidth <= tabletBP) ? true : false,
        isLaptop: (window.innerWidth > tabletBP && window.innerWidth <= laptopBP) ? true : false,
        isDesktop: (window.innerWidth > laptopBP) ? true : false,
      }

      setMediaType(currState => currState = mediaTypes);
    }

}, []); 

  useEffect(() => {
    // window.resize event listener will call handleWindowResize() whenever screen width is adjusted.
    window.addEventListener('resize', handleWindowResize);

    return () => window.removeEventListener('resize', handleWindowResize);
  },[])
  
  return{
    isMobile: mediaType.isMobile,
    isTablet: mediaType.isTablet,
    isLaptop: mediaType.isLaptop,
    isDesktop: mediaType.isDesktop,
    windowDimensions
  }
}

export default useDetectResize;

then I call it in my components like so

  const { windowDimensions, isMobile, isTablet, isLaptop, isDesktop } = useDetectResize();

Upvotes: 0

Utsav Patel
Utsav Patel

Reputation: 2889

Without registering an event listener, you cannot achieve updating of innerWidth.

Here is a way to do this with an event listener:

const handleWindowResize = useCallback(event => {
    setWindowSize(window.innerWidth);
}, []);

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

You can add and remove the event in the useEffect hook.

You can create a memoized handler using useCallback hook.

This is called a callback pattern.

In this we are simply setting the state using the updated innerWidth, its best to use the callback pattern and register the event listeners only on initial mount.

If you register the events in useEffect hook, the listeners reference along with its lexical scope is being used by the event listener but a new function is created with updated closure on each new render preventing the handler from accessing the updated state.

More on useCallback here and here.

Upvotes: 6

Ashish
Ashish

Reputation: 4330

You can use window.onresize You can check here.

function reportWindowSize() {
    console.log(window.innerHeight, window.innerWidth)
}

window.onresize = reportWindowSize;

For your use case. You can use window.addEventListener('resize', reportWindowSize) inside your hook.

const [windowWidth, setWindowWidth] = useState(window.innerWidth)
useEffect(() => {
    function reportWindowSize() {
        setWindowWidth(window.innerWidth)
        console.log(window.innerHeight, window.innerWidth)
    }
    // Trigger this function on resize
    window.addEventListener('resize', reportWindowSize)
    //  Cleanup for componentWillUnmount
    return () => window.removeEventListener('resize', reportWindowSize)
}, [])

Upvotes: 3

Related Questions