Rerender view on browser resize with React

javascript reactjs resize

172207 观看

18回复

1236 作者的声誉

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?

作者: digibake 的来源 发布者: 2013 年 9 月 25 日

回应 18


379

102119 作者的声誉

决定

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

var WindowDimensions = React.createClass({
    render: function() {
        return <span>{this.state.width} x {this.state.height}</span>;
    },
    updateDimensions: function() {
        this.setState({width: $(window).width(), height: $(window).height()});
    },
    componentWillMount: function() {
        this.updateDimensions();
    },
    componentDidMount: function() {
        window.addEventListener("resize", this.updateDimensions);
    },
    componentWillUnmount: function() {
        window.removeEventListener("resize", this.updateDimensions);
    }
});
作者: Sophie Alpert 发布者: 2013 年 9 月 25 日

5

1291 作者的声誉

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 发布者: 2015 年 6 月 19 日

14

55515 作者的声誉

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 发布者: 2015 年 7 月 22 日

121

30280 作者的声誉

@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);
    }
});
作者: André Pena 发布者: 2015 年 12 月 26 日

7

2300 作者的声誉

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 发布者: 2016 年 3 月 4 日

41

2545 作者的声誉

A very simple solution:

resize = () => this.forceUpdate()

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

componentWillUnmount() {
  window.removeEventListener('resize', this.resize)
}
作者: senornestor 发布者: 2016 年 6 月 21 日

30

453 作者的声誉

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>
    );
  }
}
作者: voice 发布者: 2016 年 7 月 28 日

9

393 作者的声誉

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 发布者: 2016 年 8 月 22 日

1

126 作者的声誉

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');
 }})
)
作者: dumorango 发布者: 2017 年 9 月 9 日

0

1126 作者的声誉

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 发布者: 2017 年 10 月 13 日

6

130193 作者的声誉

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 发布者: 2017 年 11 月 14 日

4

125 作者的声誉

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 发布者: 2018 年 1 月 6 日

0

64 作者的声誉

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

作者: Miodrag Trajanovic 发布者: 2018 年 6 月 22 日

6

1884 作者的声誉

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>
作者: Albert Olivé 发布者: 2018 年 7 月 2 日

1

566 作者的声誉

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 发布者: 2018 年 12 月 26 日

2

313 作者的声誉

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 发布者: 2019 年 3 月 4 日

9

2998 作者的声誉

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}</>
}
作者: Alex 发布者: 2019 年 4 月 24 日

0

2964 作者的声誉

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 发布者: 2019 年 7 月 10 日
32x32