57

I am developing a web app with React and need to detect when the screen size has entered the mobile break-point in order to change the state.

Specifically I need my sidenav to be collapsed when the user enters mobile mode and that is controlled with a boolean stored in the state within the component.

Embedded_Mugs
  • 2,132
  • 3
  • 21
  • 33

9 Answers9

110

What I did is adding an event listener after component mount:

componentDidMount() {
    window.addEventListener("resize", this.resize.bind(this));
    this.resize();
}

resize() {
    this.setState({hideNav: window.innerWidth <= 760});
}

componentWillUnmount() {
    window.removeEventListener("resize", this.resize.bind(this));
}

EDIT: To save state updates, I changed the "resize" a bit, just to be updated only when there is a change in the window width.

resize() {
    let currentHideNav = (window.innerWidth <= 760);
    if (currentHideNav !== this.state.hideNav) {
        this.setState({hideNav: currentHideNav});
    }
}

UPDATE: Time to use hooks! If you're component is functional, and you use hooks - then you can use the useMediaQuery hook, from react-responsive package.

import { useMediaQuery } from 'react-responsive';

...

const isMobile = useMediaQuery({ query: `(max-width: 760px)` });

After using this hook, "isMobile" will be update upon screen resize, and will re-render the component. Much nicer!

Gianfranco P.
  • 10,049
  • 6
  • 51
  • 68
Ben Cohen
  • 1,380
  • 1
  • 9
  • 12
  • 6
    You'll want to debounce or throttle that event-listener. You'll find examples of both [here](https://remysharp.com/2010/07/21/throttling-function-calls). – ivarni Jun 11 '17 at 11:15
33
const [isMobile, setIsMobile] = useState(false)
 
//choose the screen size 
const handleResize = () => {
  if (window.innerWidth < 720) {
      setIsMobile(true)
  } else {
      setIsMobile(false)
  }
}

// create an event listener
useEffect(() => {
  window.addEventListener("resize", handleResize)
})

// finally you can render components conditionally if isMobile is True or False 
tdranv
  • 1,140
  • 11
  • 38
Ummer Zaman
  • 336
  • 3
  • 4
17

Using hooks in React(16.8.0+) refering to: https://stackoverflow.com/a/36862446/1075499

import { useState, useEffect } from 'react';

function getWindowDimensions() {
  const { innerWidth: width, innerHeight: height } = window;
  return {
    width,
    height
  };
}

export default function useWindowDimensions() {
  const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());

  useEffect(() => {
    function handleResize() {
      setWindowDimensions(getWindowDimensions());
    }

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

  return windowDimensions;
}
Blessing
  • 2,450
  • 15
  • 22
  • 8
    This is a copy paste of another answer: https://stackoverflow.com/a/36862446/1075499 Please use reference for solutions that are not own – Saba Ahang Jan 20 '21 at 13:26
9

This is the same as @Ben Cohen answer but after attaching your function to eventListner, also remove it on componentWillUnmount

constructor() {
  super();
  this.state = { screenWidth: null };
  this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
}

componentDidMount() {
    window.addEventListener("resize", this.updateWindowDimensions());
}

componentWillUnmount() {
    window.removeEventListener("resize", this.updateWindowDimensions)
}

updateWindowDimensions() {
   this.setState({ screenWidth: window.innerWidth });
}
Muneeb
  • 1,469
  • 16
  • 24
  • 1
    This IS the correct answer and should be marked as such. (It should also be debounced or throttled, as ivarni commented on the above answer.) – Brightstar Jan 13 '20 at 19:10
  • When invoking the addEventListener it should only pass the this.updateWindowDimensions reference instead of invoking it. – felipenbrito Sep 01 '21 at 21:30
4

For Next.js

Here is a custom hook
import { useState, useEffect } from 'react';

export default function useScreenWidth() {

    const [windowWidth, setWindowWidth] = useState(null);

    const isWindow = typeof window !== 'undefined';

    const getWidth = () => isWindow ? window.innerWidth : windowWidth;

    const resize = () => setWindowWidth(getWidth());

    useEffect(() => {
        if (isWindow) {
            setWindowWidth(getWidth());
      
            window.addEventListener('resize', resize);
       
            return () => window.removeEventListener('resize', resize);
        }
    //eslint-disable-next-line
    }, [isWindow]);

    return windowWidth;
}

In a component, it returns the width size of the viewport, which can then be compared with a given numeric value

const widthSize = useScreenWidth()

const mobileWidth = 400

if(widthSize > mobileWidth){ 
    //logic for desktop
}

if(widthSize <= mobileWidth){ 
    //logic for mobile
}
newbie
  • 126
  • 5
3

hey I just published a npm package for this issue. Check it out https://www.npmjs.com/package/react-getscreen

import React, { Component } from 'react';
import {withGetScreen} from 'react-getscreen'

class Test extends Component {
  render() {
    if (this.props.isMobile()) return <div>Mobile</div>;
    if (this.props.isTablet()) return <div>Tablet</div>;
    return <div>Desktop</div>;
  }
}

export default withGetScreen(Test);

//or you may set your own breakpoints by providing an options object

const options = {mobileLimit: 500, tabletLimit: 800}
export default withGetScreen(Test, options);
Calin ortan
  • 126
  • 1
  • 5
  • 1
    If you ask me, browsers should make it easier to get the physical screen size rather than just the resolution, since pixel densities vary so much... – Andy Oct 31 '17 at 14:33
  • In my experience components that lay themselves out based upon screen size are less reusable, because if you were to put them in a 2 x 2 grid for example, they would render as if they were the size of the whole screen rather than a quarter of it. – Andy Oct 31 '17 at 14:35
  • well, addons like these, should only be used on something like container components – Calin ortan Nov 01 '17 at 15:21
  • Yes, they're fine for that case – Andy Nov 02 '17 at 04:34
  • how do you use it in func component? – Fatemeh Qasemkhani Jan 09 '21 at 10:09
  • same way. Imagine Test is a function component, the code outside the component stays the same. And within Test you would still get those functions as props. – Calin ortan Jan 13 '21 at 10:28
3

The react-screentype-hook library allows you to do this out of the box. https://www.npmjs.com/package/react-screentype-hook

You could use the default breakpoints it provides as follows

const screenType = useScreenType();

screenType has the following shape

  {
    isLargeDesktop: Boolean,
    isDesktop: Boolean,
    isMobile: Boolean,
    isTablet: Boolean
  }

Or you could even configure your custom breakpoints like this

const screenType = useScreenType({
    mobile: 400,
    tablet: 800,
    desktop: 1000,
    largeDesktop: 1600
  });
Mohammed Ali
  • 83
  • 1
  • 7
2

There are multiple ways to archive this first way is with CSS using this class

@media screen and (max-width: 576px) {}

any class inside this tag will only be visible when the screen is equal or less than 576px

the second way is to use the event listener

something like this

 constructor(props)
    {
        super(props);

        this.state = {
            isToggle: null
        }

        this.resizeScreen = this.resizeScreen.bind(this); 
    }
    
    
     componentDidMount() {

        window.addEventListener("resize", this.resizeScreen());

    }
    
    
    resizeScreen() {
        if(window.innerWidth === 576)
        {
            this.setState({isToggle:'I was resized'});
        }
        
    }

even with the event listener I still prefer the CSS way since we can use multiple screen sizes without further js coding.

I hope this helps!

jerryurenaa
  • 3,863
  • 1
  • 27
  • 17
0

In Functional Component, we can detect screen size by useTheme and useMediaQuery.

const theme = useTheme();
const xs = useMediaQuery(theme.breakpoints.only('xs'));
const sm = useMediaQuery(theme.breakpoints.only('sm'));
const md = useMediaQuery(theme.breakpoints.only('md'));
const lg = useMediaQuery(theme.breakpoints.only('lg'));
const xl = useMediaQuery(theme.breakpoints.only('xl'));

Haseeb Butt
  • 109
  • 5