491

How can I get React to re-render the view when the browser window is resized?

Background

I have some blocks that I want to layout individually on the page, however I also want them to update when the browser window changes. The very end result will be something like Ben Holland's Pinterest layout, but written using React not just jQuery. I’m still a way off.

Code

Here’s my app:

var MyApp = React.createClass({
  //does the http get from the server
  loadBlocksFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      mimeType: 'textPlain',
      success: function(data) {
        this.setState({data: data.events});
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentWillMount: function() {
    this.loadBlocksFromServer();

  },    
  render: function() {
    return (
        <div>
      <Blocks data={this.state.data}/>
      </div>
    );
  }
});

React.renderComponent(
  <MyApp url="url_here"/>,
  document.getElementById('view')
)

Then I have the Block component (equivalent to a Pin in the above Pinterest example):

var Block = React.createClass({
  render: function() {
    return (
        <div class="dp-block" style={{left: this.props.top, top: this.props.left}}>
        <h2>{this.props.title}</h2>
        <p>{this.props.children}</p>
        </div>
    );
  }
});

and the list/collection of Blocks:

var Blocks = React.createClass({

  render: function() {

    //I've temporarily got code that assigns a random position
    //See inside the function below...

    var blockNodes = this.props.data.map(function (block) {   
      //temporary random position
      var topOffset = Math.random() * $(window).width() + 'px'; 
      var leftOffset = Math.random() * $(window).height() + 'px'; 
      return <Block order={block.id} title={block.summary} left={leftOffset} top={topOffset}>{block.description}</Block>;
    });

    return (
        <div>{blockNodes}</div>
    );
  }
});

Question

Should I add jQuery’s window resize? If so, where?

$( window ).resize(function() {
  // re-render the component
});

Is there a more “React” way of doing this?

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
digibake
  • 4,921
  • 3
  • 12
  • 5

25 Answers25

882

Using React Hooks:

You can define a custom Hook that listens to the window resize event, something like this:

import React, { useLayoutEffect, useState } from 'react';

function useWindowSize() {
  const [size, setSize] = useState([0, 0]);
  useLayoutEffect(() => {
    function updateSize() {
      setSize([window.innerWidth, window.innerHeight]);
    }
    window.addEventListener('resize', updateSize);
    updateSize();
    return () => window.removeEventListener('resize', updateSize);
  }, []);
  return size;
}

function ShowWindowDimensions(props) {
  const [width, height] = useWindowSize();
  return <span>Window size: {width} x {height}</span>;
}

The advantage here is the logic is encapsulated, and you can use this Hook anywhere you want to use the window size.

Using React classes:

You can listen in componentDidMount, something like this component which just displays the window dimensions (like <span>Window size: 1024 x 768</span>):

import React from 'react';

class ShowWindowDimensions extends React.Component {
  state = { width: 0, height: 0 };
  render() {
    return <span>Window size: {this.state.width} x {this.state.height}</span>;
  }
  updateDimensions = () => {
    this.setState({ width: window.innerWidth, height: window.innerHeight });
  };
  componentDidMount() {
    window.addEventListener('resize', this.updateDimensions);
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.updateDimensions);
  }
}
Matt Fletcher
  • 8,182
  • 8
  • 41
  • 60
Sophie Alpert
  • 139,698
  • 36
  • 220
  • 238
  • 6
    How does this work? The `this.updateDimensions` passed to `addEventListener` is just a bare function reference which will have no value for `this` when called. Should an anonymous function, or a .bind() call be used to add `this`, or have I misunderstood? – fadedbee Feb 20 '14 at 13:01
  • 27
    @chrisdew I'm a little late here, but React auto-binds `this` for any methods defined directly on the component. – Michelle Tilley Jun 30 '14 at 00:01
  • 1
    @MichelleTilley perhaps this has changed with ES6 and `React.Component` instead of `React.createClass`, but I had to change the event listeners to `window.addEventListener("resize", ::this.updateDimensions);`and `window.removeEventListener("resize", ::this.updateDimensions);` in order for this to work. – Matt Dell Jan 28 '16 at 22:59
  • 4
    @MattDell yes, ES6 classes are just normal classes, so no auto-binding with them. – Michelle Tilley Jan 28 '16 at 23:01
  • 34
    No jQuery needed – use `innerHeight` and `innerWidth` from `window`. And you can skip `componentWillMount` if you use `getInitialState` to set `height` and `width`. – sighrobot Oct 11 '16 at 22:11
  • 3
    @MattDell looks like the `::` bind syntax is out for now https://www.sitepoint.com/bind-javascripts-this-keyword-react/ "the bind operator (::) is not going to be part of ES7, as the ES7 features set was frozen in february, the bind operator is a proposal for ES8" – sleep May 03 '17 at 06:07
  • Can we ditch the `$` for jQuery – Athman Sep 19 '18 at 15:04
  • 2
    Can/should this be done for every component that needs this logic? – Galupuf Dec 05 '18 at 17:48
  • 1
    I believe you don't need `useLayoutEffect` for this – woojoo666 Oct 02 '19 at 21:05
  • @woojoo666 Since in most cases this would be used to affect the visual appearance of a component, `useLayoutEffect` is appropriate to prevent flickering. – Sophie Alpert Oct 06 '19 at 18:19
  • 1
    @SophieAlpert the hook is not really used to directly modify the appearance though, the hook is merely used to attach an event listener – woojoo666 Oct 07 '19 at 09:24
  • It calls updateSize from the effect. You're right that the event listener could be moved to a useEffect. – Sophie Alpert Oct 07 '19 at 23:13
  • Thanks for this! Works great. The only change I made was initializing state as [window.innerWidth, window.innerHeight] rather than [0, 0], so that any useEffect hooks that are listening to changes in these values, don't get an initial 0, but rather the correct window dimensions on first render. – Matt. Aug 20 '20 at 21:33
  • this reloads the page anytime keyboard is in focus – Raphael Inyang Jan 07 '21 at 09:30
135

@SophieAlpert is right, +1, I just want to provide a modified version of her solution, without jQuery, based on this answer.

var WindowDimensions = React.createClass({
    render: function() {
        return <span>{this.state.width} x {this.state.height}</span>;
    },
    updateDimensions: function() {

    var w = window,
        d = document,
        documentElement = d.documentElement,
        body = d.getElementsByTagName('body')[0],
        width = w.innerWidth || documentElement.clientWidth || body.clientWidth,
        height = w.innerHeight|| documentElement.clientHeight|| body.clientHeight;

        this.setState({width: width, height: height});
        // if you are using ES2015 I'm pretty sure you can do this: this.setState({width, height});
    },
    componentWillMount: function() {
        this.updateDimensions();
    },
    componentDidMount: function() {
        window.addEventListener("resize", this.updateDimensions);
    },
    componentWillUnmount: function() {
        window.removeEventListener("resize", this.updateDimensions);
    }
});
davnicwil
  • 28,487
  • 16
  • 107
  • 123
Andre Pena
  • 56,650
  • 48
  • 196
  • 243
  • only works when you have your IE-related polyfills set up for vanilla JS event listeners – nnnn Jan 27 '16 at 17:45
  • @nnnn can you elaborate? I admit I only tested this in Chrome. Are you saying the window.addEventListener isn't going to work on IE without polyfills? – Andre Pena Jan 27 '16 at 21:02
  • 2
    @andrerpena http://caniuse.com/#search=addeventlistener indicates ie8 would have a problem – nnnn Jan 27 '16 at 23:36
  • 2
    @nnnn. I see. Yes.. So my solution doesn't work on IE 8, but works from 9 on :). Thanks. – Andre Pena Jan 28 '16 at 16:20
  • 49
    Does anyone really still care about IE8? Or is it just habit? – frostymarvelous Feb 29 '16 at 15:41
  • @LessNoviceProgrammer last year, when I made that comment, we did support IE8 on the public portion of our product (and used React on a different section). glad to report that we're moving on from it soon. but whatcha gonna do when your code touches 14-15 million domains internationally? – nnnn Sep 20 '16 at 06:10
  • IE is such a pain, really... – Jinwook Kim Mar 15 '23 at 02:24
66

A very simple solution:

resize = () => this.forceUpdate()

componentDidMount() {
  window.addEventListener('resize', this.resize)
}

componentWillUnmount() {
  window.removeEventListener('resize', this.resize)
}
senornestor
  • 4,075
  • 2
  • 33
  • 33
  • 16
    Don't forget to throttle the force update or it's going to look very glitchy. – k2snowman69 Nov 11 '16 at 19:52
  • 1
    This is the best solutions if it is throttled. I mean if the `forceUpdate` is applied conditionally – asmmahmud Apr 15 '18 at 16:34
  • @k2snowman69 please what are you talking about, is the forceUpdate throttled here? Seems that yes since it is thrilled only in case of resizing – Webwoman Jun 23 '19 at 02:52
  • 2
    It's not the forceUpdate (the thing being called) that needs to be throttled, it's the resize event firing that needs to be throttled (the thing firing). Resize events can technically be called at every pixel as you resize a window from large to small. When a user does this quickly, that's more events than you care about. Worse off, you're binding the UI thread to the Javascript thread meaning your app will start to get a seriously slow feeling as it tries to handle which each event individually. – k2snowman69 Jun 24 '19 at 03:10
  • 2
    Instead of running the resize function at every pixel, you run it at some short periodic amount of time to give the illusion of fluidity which you always handle the first and last event thus giving the feel that it's fluidly handling the resize. – k2snowman69 Jun 24 '19 at 03:16
  • `this.forceUpdate()` was only I need. Thanks – Mussa Charles Oct 13 '21 at 22:39
55

It's a simple and short example of using es6 without jQuery.

import React, { Component } from 'react';

export default class CreateContact extends Component {
  state = {
    windowHeight: undefined,
    windowWidth: undefined
  }

  handleResize = () => this.setState({
    windowHeight: window.innerHeight,
    windowWidth: window.innerWidth
  });

  componentDidMount() {
    this.handleResize();
    window.addEventListener('resize', this.handleResize)
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize)
  }

  render() {
    return (
      <span>
        {this.state.windowWidth} x {this.state.windowHeight}
      </span>
    );
  }
}

hooks

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

let App = () => {
  const [windowWidth, setWindowWidth] = useState(0);
  const [windowHeight, setWindowHeight] = useState(0);
  let resizeWindow = () => {
    setWindowWidth(window.innerWidth);
    setWindowHeight(window.innerHeight);
  };

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

  return (
    <div>
      <span>
        {windowWidth} x {windowHeight}
      </span>
    </div>
  );
};
voice
  • 830
  • 10
  • 17
  • 5
    This is a nice, succinct answer but AFAICT has one bug: unless I am mistaken, the `::` bind operator returns a new value each time it is applied. So your event listener will not actually get unregistered, since your `removeEventListener` ends up passing a different function than what was originally passed to `addEventListener`. – natevw Dec 14 '16 at 04:02
42

Update in 2020. For React devs who care performance seriously.

Above solutions do work BUT will re-render your components whenever the window size changes by a single pixel.

This often causes performance issues so I wrote useWindowDimension hook that debounces the resize event for a short period of time. e.g 100ms

import React, { useState, useEffect } from 'react';

export function useWindowDimension() {
  const [dimension, setDimension] = useState([
    window.innerWidth,
    window.innerHeight,
  ]);
  useEffect(() => {
    const debouncedResizeHandler = debounce(() => {
      console.log('***** debounced resize'); // See the cool difference in console
      setDimension([window.innerWidth, window.innerHeight]);
    }, 100); // 100ms
    window.addEventListener('resize', debouncedResizeHandler);
    return () => window.removeEventListener('resize', debouncedResizeHandler);
  }, []); // Note this empty array. this effect should run only on mount and unmount
  return dimension;
}

function debounce(fn, ms) {
  let timer;
  return _ => {
    clearTimeout(timer);
    timer = setTimeout(_ => {
      timer = null;
      fn.apply(this, arguments);
    }, ms);
  };
}

Use it like this.

function YourComponent() {
  const [width, height] = useWindowDimension();
  return <>Window width: {width}, Window height: {height}</>;
}
NFT Master
  • 1,400
  • 12
  • 13
  • 2
    I used this same idea to only "fire" the hook when the resize event crosses a CSS breakpoint boundary, i.e., resizing from mobile size to table or desktop size. – Seth Oct 28 '20 at 10:04
  • Hi @Seth, could you please share your solution? I need to do the same thing. Thanks – Jeff May 10 '21 at 11:25
30

As of React 16.8 you can use Hooks!

/* globals window */
import React, { useState, useEffect } from 'react'
import _debounce from 'lodash.debounce'

const Example = () => {
  const [width, setWidth] = useState(window.innerWidth)

  useEffect(() => {
    const handleResize = _debounce(() => setWidth(window.innerWidth), 100)

    window.addEventListener('resize', handleResize);

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

  return <>Width: {width}</>
}
noetix
  • 4,773
  • 3
  • 26
  • 47
18

Edit 2018: now React has first class support for context


I will try to give a generic answer, that targets this specific problem but a more general problem also.

If you don't care about side effects libs, you can simply use something like Packery

If you use Flux, you could create a store that contain the window properties so that you keep a pure render function without having to query the window object everytime.

In other cases where you want to build a responsive website but you prefer React inline styles to media queries, or want the HTML/JS behavior to change according to window width, keep reading:

What is React context and why I talk about it

React context an is not in the public API and permits to pass properties to a whole hierarchy of components.

React context is particularly useful to pass to your whole app things that never changes (it is used by many Flux frameworks through a mixin). You can use it to store app business invariants (like the connected userId, so that it's available everywhere).

But it can also be used to store things that can change. The problem is that when the context changes, all the components that use it should be re-rendered and it is not easy to do so, the best solution is often to unmount/remount the whole app with the new context. Remember forceUpdate is not recursive.

So as you understand, context is practical, but there's a performance impact when it changes, so it should rather not change too often.

What to put in context

  • Invariants: like the connected userId, sessionToken, whatever...
  • Things that don't change often

Here are things that don't change often:

The current user language:

It does not change very oftenly, and when it does, as the whole app is translated we have to re-render everything: a very nice usecase of hot langage change

The window properties

Width and height to not change often but when we do our layout and behavior may have to adapt. For the layout sometimes it's easy to customize with CSS mediaqueries, but sometimes it's not and requires a different HTML structure. For the behavior you have to handle this with Javascript.

You don't want to re-render everything on every resize event, so you have to debounce the resize events.

What I understand of your problem is that you want to know how many items to display according to the screen width. So you have first to define responsive breakpoints, and enumerate the number of different layout types you can have.

For example:

  • Layout "1col", for width <= 600
  • Layout "2col", for 600 < width < 1000
  • Layout "3col", for 1000 <= width

On resize events (debounced), you can easily get the current layout type by querying the window object.

Then you can compare the layout type with the former layout type, and if it has changed, re-render the app with a new context: this permits to avoid re-rendering the app at all when the user has trigger resize events but actually the layout type has not changed, so you only re-render when required.

Once you have that, you can simply use the layout type inside your app (accessible through the context) so that you can customize the HTML, behavior, CSS classes... You know your layout type inside the React render function so this means you can safely write responsive websites by using inline styles, and don't need mediaqueries at all.

If you use Flux, you can use a store instead of React context, but if your app has a lot of responsive components maybe it's simpler to use context?

Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419
12

I use @senornestor 's solution, but to be entirely correct you have to remove the event listener as well:

componentDidMount() {
    window.addEventListener('resize', this.handleResize);
}

componentWillUnmount(){
    window.removeEventListener('resize', this.handleResize);
}

handleResize = () => {
    this.forceUpdate();
};

Otherwise you 'll get the warning:

Warning: forceUpdate(...): Can only update a mounted or mounting component. This usually means you called forceUpdate() on an unmounted component. This is a no-op. Please check the code for the XXX component.

gkri
  • 1,627
  • 3
  • 18
  • 27
  • 1
    You're getting the warning for a reason: what you're doing is bad practice. Your render() function should be a pure function of props and state. In your case you should save the new size in the state. – zoran404 Dec 05 '18 at 12:46
10

I would skip all of the above answers and start using the react-dimensions Higher Order Component.

https://github.com/digidem/react-dimensions

Just add a simple import and a function call, and you can access this.props.containerWidth and this.props.containerHeight in your component.

// Example using ES6 syntax
import React from 'react'
import Dimensions from 'react-dimensions'

class MyComponent extends React.Component {
  render() (
    <div
      containerWidth={this.props.containerWidth}
      containerHeight={this.props.containerHeight}
    >
    </div>
  )
}

export default Dimensions()(MyComponent) // Enhanced component
MindJuice
  • 4,121
  • 3
  • 29
  • 41
  • 1
    This does not give the size of the window, just the container. – mjtamlyn May 10 '16 at 15:01
  • 3
    Yes, that's the whole point. The size of the window is trivial to find. The size of a container is much harder to find and much more useful for a React component. The latest version of `react-dimensions` even works for programmatically changed dimensions (e.g., the size of a div changed, which affects the size of your container, so you need to update your size). Ben Alpert's answer only helps on browser window resize events. See here: https://github.com/digidem/react-dimensions/issues/4 – MindJuice May 11 '16 at 00:44
  • Yeah react-dimensions is a great library. It's not always sufficient though - I have layouts which depend on both the size of the container and the size of the window, so as to scale certain things down so they fit. It's possible I can restructure some of these to use `max-height: XXvh`, but I don't think it's always sufficient. – mjtamlyn May 11 '16 at 13:11
  • 1
    `react-dimensions` handles changes in the size of the window, flexbox layout changes and changes due to JavaScript resizing. I think that covers it. Do you have an example that it doesn't handle properly? – MindJuice May 11 '16 at 20:32
  • In one use of my use cases where it wouldn't be sufficient - we have a layout algorithm which places fits scaleable blocks of content on the page in rows. Within certain bounds, we want rows to fit about 2.5 rows to the height of the screen. Width is fine to do from container size, but height needs to know window size. – mjtamlyn May 12 '16 at 13:47
  • 2
    Window size is trivial to get. No need for a library for that: `window.innerWidth`, `window.innerHeight`. `react-dimensions` solves the more important part of the problem, and also triggers your layout code when the window is resized (as well as when the container size changes). – MindJuice May 12 '16 at 18:39
  • 2
    This project is no longer being actively maintained. – Parabolord Jun 16 '18 at 16:35
10

Wanted to share this pretty cool thing I just found using window.matchMedia

const mq = window.matchMedia('(max-width: 768px)');

  useEffect(() => {
    // initial check to toggle something on or off
    toggle();

    // returns true when window is <= 768px
    mq.addListener(toggle);

    // unmount cleanup handler
    return () => mq.removeListener(toggle);
  }, []);

  // toggle something based on matchMedia event
  const toggle = () => {
    if (mq.matches) {
      // do something here
    } else {
      // do something here
    }
  };

.matches will return true or false if the window is higher or lower than the specified max-width value, this means there is no need to throttle the listener, as the matchMedia only fires one time when the boolean changes.

My code can easily be adjusted to include useState to save the boolean matchMedia returns, and use it to conditionally render a component, fire action etc.

Riley Brown
  • 101
  • 2
  • 4
  • 1
    It's very useful in case of that when I want to apply specific styles to like Form Input components acrroding to range of window size to avoid their width gets too long or too short. Thanks! – Rong.l Sep 14 '20 at 14:21
  • Very nice solution. Pretty cool thing as you said! – Cristianjs19 Jan 04 '21 at 08:10
  • Sweet and clean. Way to go – itwabi May 03 '21 at 21:43
  • in 2022: `The declaration was marked as deprecated` – Aliton Oliveira Nov 22 '22 at 21:43
  • `.addListener` method is declared deprecated on docs but the job can still be done using matchMedia() method. There is a functional example here: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#monitoring_screen_resolution_or_zoom_level_changes – Elyas Karimi May 14 '23 at 17:11
9

This code is using the new React context API:

  import React, { PureComponent, createContext } from 'react';

  const { Provider, Consumer } = createContext({ width: 0, height: 0 });

  class WindowProvider extends PureComponent {
    state = this.getDimensions();

    componentDidMount() {
      window.addEventListener('resize', this.updateDimensions);
    }

    componentWillUnmount() {
      window.removeEventListener('resize', this.updateDimensions);
    }

    getDimensions() {
      const w = window;
      const d = document;
      const documentElement = d.documentElement;
      const body = d.getElementsByTagName('body')[0];
      const width = w.innerWidth || documentElement.clientWidth || body.clientWidth;
      const height = w.innerHeight || documentElement.clientHeight || body.clientHeight;

      return { width, height };
    }

    updateDimensions = () => {
      this.setState(this.getDimensions());
    };

    render() {
      return <Provider value={this.state}>{this.props.children}</Provider>;
    }
  }

Then you can use it wherever you want in your code like this:

<WindowConsumer>
  {({ width, height }) =>  //do what you want}
</WindowConsumer>
Gianfranco P.
  • 10,049
  • 6
  • 51
  • 68
Albert Olivé Corbella
  • 4,061
  • 7
  • 48
  • 66
7

Not sure if this is the best approach, but what worked for me was first creating a Store, I called it WindowStore:

import {assign, events} from '../../libs';
import Dispatcher from '../dispatcher';
import Constants from '../constants';

let CHANGE_EVENT = 'change';
let defaults = () => {
    return {
        name: 'window',
        width: undefined,
        height: undefined,
        bps: {
            1: 400,
            2: 600,
            3: 800,
            4: 1000,
            5: 1200,
            6: 1400
        }
    };
};
let save = function(object, key, value) {
    // Save within storage
    if(object) {
        object[key] = value;
    }

    // Persist to local storage
    sessionStorage[storage.name] = JSON.stringify(storage);
};
let storage;

let Store = assign({}, events.EventEmitter.prototype, {
    addChangeListener: function(callback) {
        this.on(CHANGE_EVENT, callback);
        window.addEventListener('resize', () => {
            this.updateDimensions();
            this.emitChange();
        });
    },
    emitChange: function() {
        this.emit(CHANGE_EVENT);
    },
    get: function(keys) {
        let value = storage;

        for(let key in keys) {
            value = value[keys[key]];
        }

        return value;
    },
    initialize: function() {
        // Set defaults
        storage = defaults();
        save();
        this.updateDimensions();
    },
    removeChangeListener: function(callback) {
        this.removeListener(CHANGE_EVENT, callback);
        window.removeEventListener('resize', () => {
            this.updateDimensions();
            this.emitChange();
        });
    },
    updateDimensions: function() {
        storage.width =
            window.innerWidth ||
            document.documentElement.clientWidth ||
            document.body.clientWidth;
        storage.height =
            window.innerHeight ||
            document.documentElement.clientHeight ||
            document.body.clientHeight;
        save();
    }
});

export default Store;

Then I used that store in my components, kinda like this:

import WindowStore from '../stores/window';

let getState = () => {
    return {
        windowWidth: WindowStore.get(['width']),
        windowBps: WindowStore.get(['bps'])
    };
};

export default React.createClass(assign({}, base, {
    getInitialState: function() {
        WindowStore.initialize();

        return getState();
    },
    componentDidMount: function() {
        WindowStore.addChangeListener(this._onChange);
    },
    componentWillUnmount: function() {
        WindowStore.removeChangeListener(this._onChange);
    },
    render: function() {
        if(this.state.windowWidth < this.state.windowBps[2] - 1) {
            // do something
        }

        // return
        return something;
    },
    _onChange: function() {
        this.setState(getState());
    }
}));

FYI, these files were partially trimmed.

David Sinclair
  • 4,187
  • 3
  • 17
  • 12
7

You don't necessarily need to force a re-render.

This might not help OP, but in my case I only needed to update the width and height attributes on my canvas (which you can't do with CSS).

It looks like this:

import React from 'react';
import styled from 'styled-components';
import {throttle} from 'lodash';

class Canvas extends React.Component {

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

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

    resize = throttle(() => {
        this.canvas.width = this.canvas.parentNode.clientWidth;
        this.canvas.height = this.canvas.parentNode.clientHeight;
    },50)

    setRef = node => {
        this.canvas = node;
    }

    render() {
        return <canvas className={this.props.className} ref={this.setRef} />;
    }
}

export default styled(Canvas)`
   cursor: crosshair;
`
mpen
  • 272,448
  • 266
  • 850
  • 1,236
6

I know this has been answered but just thought I'd share my solution as the top answer, although great, may now be a little outdated.

    constructor (props) {
      super(props)

      this.state = { width: '0', height: '0' }

      this.initUpdateWindowDimensions = this.updateWindowDimensions.bind(this)
      this.updateWindowDimensions = debounce(this.updateWindowDimensions.bind(this), 200)
    }

    componentDidMount () {
      this.initUpdateWindowDimensions()
      window.addEventListener('resize', this.updateWindowDimensions)
    }

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

    updateWindowDimensions () {
      this.setState({ width: window.innerWidth, height: window.innerHeight })
    }

The only difference really is that I'm debouncing (only running every 200ms) the updateWindowDimensions on the resize event to increase performance a bit, BUT not debouncing it when it's called on ComponentDidMount.

I was finding the debounce made it quite laggy to mount sometimes if you have a situation where it's mounting often.

Just a minor optimisation but hope it helps someone!

Matt Wills
  • 676
  • 6
  • 11
5

There's a lot of cooks in this kitchen but I'm going to toss my hat in regardless. None of these use requestAnimationFrame which I would argue is the most performant.

Here is an example using React hooks and requestAnimationFrame. This also uses pure js without any libraries like lodash (which I avoid at all costs due to bundle size).

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

const getSize = () => {
  return { 
    width: window.innerWidth,
    height: window.innerHeight,
  };
};
 
export function useResize() {
 
  const [size, setSize] = useState(getSize());
 
  const handleResize = useCallback(() => {
    let ticking = false;
    if (!ticking) {
      window.requestAnimationFrame(() => {
        setSize(getSize());
        ticking = false;
      });
      ticking = true;
    } 
  }, []);

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

Here is a gist showing it in use: Img.tsx with useResize. Alternatively, you can see it in my repo here for more context.

Some resources on why you should do this instead of debouncing your function:

Thank you for coming to my Ted Talk.

  • Can you please provide a link to where your `useResize()` function is used within your repo? I ran into issues using it initially because I was trying to set it as a `const` outside of my component's function, which caused `warning: Invalid hook usage`. When I moved the assignment inside the component, everything worked great. – Swirle13 Jun 26 '23 at 18:52
  • Unfortunately my repo is now private but the cause for that is you cannot use hooks outside of a React context. https://legacy.reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions – Patrick Michaelsen Aug 23 '23 at 01:16
4
componentDidMount() {

    // Handle resize
    window.addEventListener('resize', this.handleResize);
}




handleResize = () => {
    this.renderer.setSize(this.mount.clientWidth, this.mount.clientHeight);
    this.camera.aspect = this.mount.clientWidth / this.mount.clientHeight;
    this.camera.updateProjectionMatrix();
};

Only need to define resize event function.

Then update the renderers size ( canvas ), assign a new aspect ratio for the camera.

Unmounting and remouting is a crazy solution in my opinion....

below is the mount if needed.

            <div
                className={this.state.canvasActive ? 'canvasContainer isActive' : 'canvasContainer'}
                ref={mount => {
                    this.mount = mount;
                }}
            />
Nicolay Hekkens
  • 530
  • 5
  • 18
3

Just to improve on @senornestor's solution to use forceUpdate and @gkri's solution to removing the resize event listener on component unmount:

  1. don't forget to throttle (or debounce) the call to resize
  2. make sure to bind(this) in the constructor
import React from 'react'
import { throttle } from 'lodash'

class Foo extends React.Component {
  constructor(props) {
    super(props)
    this.resize = throttle(this.resize.bind(this), 100)
  }

  resize = () => this.forceUpdate()

  componentDidMount() {
    window.addEventListener('resize', this.resize)
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.resize)
  }

  render() {
    return (
      <div>{window.innerWidth} x {window.innerHeight}</div>
    )
  }
}

Another method is to just use a "dummy" state instead of forceUpdate:

import React from 'react'
import { throttle } from 'lodash'

class Foo extends React.Component {
  constructor(props) {
    super(props)
    this.state = { foo: 1 }
    this.resize = throttle(this.resize.bind(this), 100)
  }

  resize = () => this.setState({ foo: 1 })

  componentDidMount() {
    window.addEventListener('resize', this.resize)
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.resize)
  }

  render() {
    return (
      <div>{window.innerWidth} x {window.innerHeight}</div>
    )
  }
}
kimbaudi
  • 13,655
  • 9
  • 62
  • 74
2

Thank you all for the answers. Here's my React + Recompose. It's a High Order Function that includes the windowHeight and windowWidth properties to the component.

const withDimensions = compose(
 withStateHandlers(
 ({
   windowHeight,
   windowWidth
 }) => ({
   windowHeight: window.innerHeight,
   windowWidth: window.innerWidth
 }), {
  handleResize: () => () => ({
    windowHeight: window.innerHeight,
    windowWidth: window.innerWidth
  })
 }),
 lifecycle({
   componentDidMount() {
   window.addEventListener('resize', this.props.handleResize);
 },
 componentWillUnmount() {
  window.removeEventListener('resize');
 }})
)
Brian Burns
  • 20,575
  • 8
  • 83
  • 77
dumorango
  • 136
  • 1
  • 4
2

Had to bind it to 'this' in the constructor to get it working with Class syntax

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.resize = this.resize.bind(this)      
  }
  componentDidMount() {
    window.addEventListener('resize', this.resize)
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.resize)
  }
}
Jim Perris
  • 2,545
  • 1
  • 12
  • 15
2

https://github.com/renatorib/react-sizes is a HOC to do this while still maintaining good performance.

import React from 'react'
import withSizes from 'react-sizes'

@withSizes(({ width }) => ({ isMobile: width < 480 }))
class MyComponent extends Component {
  render() {
    return <div>{this.props.isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
  }
}

export default MyComponent
Russell Cohen
  • 717
  • 4
  • 7
1

Try this :-

resize = () => this.forceUpdate()

componentDidMount() {
  window.addEventListener('resize', this.resize)
}

componentWillUnmount() {
  window.removeEventListener('resize', this.resize)
}
rahulfaujdar
  • 317
  • 3
  • 12
0

For this reason better is if you use this data from CSS or JSON file data, and then with this data setting new state with this.state({width: "some value",height:"some value" }); or writing code who use data of width screen data in self work if you wish responsive show images

0

import React, {useState} from 'react';

type EventListener = () => void
let eventListener: EventListener | undefined;

function setEventListener(updateSize: (size: number[]) => void){
    if(eventListener){
        window.removeEventListener('resize',eventListener);
    }
    eventListener = () => updateSize([window.innerWidth, window.innerHeight]);

    return eventListener as EventListener;
}

function setResizer(updateSize: (size: number[]) => void) {
    window.addEventListener(
        'resize',
        setEventListener(updateSize)
    );
}

function useWindowSizeTableColumns() {
    const [size, setSize] = useState([
        window.innerWidth || 0,
        window.innerHeight || 0
    ]);

    setResizer(updateSize);

    return size;

    function updateSize(s: number[]) {
        if(size.some((v, i) => v !== s[i])){
            setSize(s);
        }
    }
}

export default useWindowSize;
tannerman
  • 470
  • 3
  • 15
  • Suggestion: Some additional explanation for how to use the code in this answer would make it more valuable. – Manfred Jul 18 '21 at 03:40
0

In index.js:

function render() {
  ReactDOM.render(<App />, document.getElementById('root'));
}

render();

window.addEventListener('resize', render);

Re-rendering forces recalculating everything that depends on variables that React doesn't detect by itself, such as window.innerWidth/innerHeight, localStorage, etc, while keeping the app state the same.

If need to re-render in other situations, can also export this render() function and use it in other places.

I'm not sure about the impact of this on performace though (as it may be re-rendering everything or just what changed), but for me it seems fast enough when resizing.

Benur21
  • 109
  • 7
0

For anyone looking for a Typescript support, I've written the below hook based on @Lead Developer's answer:

import { useState, useEffect } from 'react';


export const debounce = <A extends unknown[]>(callback: (...args: A) => unknown, msDelay: number) => {
    let timer: NodeJS.Timeout | undefined;

    return (...args: A) => {
        clearTimeout(timer);

        timer = setTimeout(() => {
            timer = undefined;
            callback(...args);
        }, msDelay);
    };
};

export const useWindowDimension = (msDelay = 100) => {
    const [dimension, setDimension] = useState({
        width: window.innerWidth,
        height: window.innerHeight,
    });

    useEffect(() => {
        const resizeHandler = () => {
            setDimension({
                width: window.innerWidth,
                height: window.innerHeight
            });
        };

        const handler = msDelay <= 0 ? resizeHandler : debounce(resizeHandler, msDelay);

        window.addEventListener('resize', handler);

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

    return dimension;
};

export type Dimension = ReturnType<typeof useWindowDimension>;

The nice thing is obviously the Typescript support, but also the option to control the delay time (in ms) that is defaulted to 100ms. If the delay time is 0 or less, it will not debounce the handler.
Another difference is that the hook returns an object of type Dimension, which is:

type Dimension = {
    width: number;
    height: number;
}

This way it's clear to everyone what the hook actually returns.

Elyasaf755
  • 2,239
  • 18
  • 24