ChatGPT解决这个技术问题 Extra ChatGPT

Rerender view on browser resize with React

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?


M
Matt Fletcher

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);
  }
}

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?
@chrisdew I'm a little late here, but React auto-binds this for any methods defined directly on the component.
@MattDell yes, ES6 classes are just normal classes, so no auto-binding with them.
No jQuery needed – use innerHeight and innerWidth from window. And you can skip componentWillMount if you use getInitialState to set height and width.
@MattDell looks like the :: bind syntax is out for now 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"
d
davnicwil

@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);
    }
});

only works when you have your IE-related polyfills set up for vanilla JS event listeners
@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?
@andrerpena caniuse.com/#search=addeventlistener indicates ie8 would have a problem
@nnnn. I see. Yes.. So my solution doesn't work on IE 8, but works from 9 on :). Thanks.
Does anyone really still care about IE8? Or is it just habit?
s
senornestor

A very simple solution:

resize = () => this.forceUpdate()

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

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

Don't forget to throttle the force update or it's going to look very glitchy.
Also don't forget to remove the listener on componentWillUnmount()!
This is the best solutions if it is throttled. I mean if the forceUpdate is applied conditionally
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.
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.
v
voice

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>
  );
};

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.
n
noetix

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}</>
}

Just implemented this, works great on window load and on resize
L
Lead Developer

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}</>;
}

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.
Hi @Seth, could you please share your solution? I need to do the same thing. Thanks
S
Sebastien Lorber

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?


g
gkri

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.


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.
M
MindJuice

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

This does not give the size of the window, just the container.
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: github.com/digidem/react-dimensions/issues/4
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?
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).
This project is no longer being actively maintained.
G
Gianfranco P.

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>

D
David Sinclair

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.


Store state should only change in response to a dispatched action. This is definitely not a good way to do this.
@frostymarvelous indeed, perhaps better relocated into the component as in the other answers.
@DavidSinclair Or even better, onresize could dispatch a WindowResizedAction.
This is overkill.
m
mpen

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;
`

R
Riley Brown

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.


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!
Very nice solution. Pretty cool thing as you said!
Sweet and clean. Way to go
M
Matt Wills

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!


Where is definition of initUpdateWindowDimensions method?
It’s defined in the constructor :) it’s just updateWindowDimensions bound to the component but without the debounce.
N
Nicolay Hekkens
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;
                }}
            />

k
kimbaudi

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

don't forget to throttle (or debounce) the call to resize 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>
    )
  }
}

J
Jim Perris

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)
  }
}

B
Brian Burns

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');
 }})
)

R
Russell Cohen

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

r
rahulfaujdar

Try this :-

resize = () => this.forceUpdate()

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

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

M
Miodrag Trajanovic

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


t
tannerman

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;


Suggestion: Some additional explanation for how to use the code in this answer would make it more valuable.
B
Benur21

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.