2

I would like to set the state of a component based on the current size of the browser window. The server-side rendering has been used (React+Redux). I was thinking about using the Redux store as a glue - just to update the store on resize. Is there any other/better solution that doesn't involve Redux. Thanks.

class FocalImage extends Component {

  // won't work - the backend rendering is used
  // componentDidMount() {
  //  window.addEventListener(...);
  //}

  //componentWillUnmount() {
  //  window.removeEventListener('resize' ....);
  //}

  onresize(e) {
    //
  }

  render() {
    const {src, className, nativeWidth, nativeHeight} = this.props;
    return (
      <div className={cn(className, s.focalImage)}>
        <div className={s.imageWrapper}>
          <img src={src} className={_compare_ratios_ ? s.tall : s.wide}/>
        </div>
      </div>
    );
  }
}
1
  • componentDidMount is not called on server-side rendering. Commented Nov 14, 2016 at 1:00

2 Answers 2

5

I have a resize helper component that I can pass a function to, which looks like this:

class ResizeHelper extends React.Component {

    static propTypes = {
        onWindowResize: PropTypes.func,
    };

    constructor() {
        super();
        this.handleResize = this.handleResize.bind(this);
    }

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

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

    handleResize(event) {
        if ('function' === typeof this.props.onWindowResize) {
            // we want this to fire immediately the first time but wait to fire again
            // that way when you hit a break it happens fast and only lags if you hit another break immediately
            if (!this.resizeTimer) {
                this.props.onWindowResize(event);
                this.resizeTimer = setTimeout(() => {
                    this.resizeTimer = false;
                }, 250); // this debounce rate could be passed as a prop
            }
        }
    }

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

Then any component that needs to do something on resize can use it like this:

<ResizeHelper onWindowResize={this.handleResize} />

You also may need to call the passed function once on componentDidMount to set up the UI. Since componentDidMount and componentWillUnmount never get called on the server this works perfectly in my isomorphic App.

Sign up to request clarification or add additional context in comments.

Comments

2

My solution is to handle resize event on the top-most level and pass it down to my top-most component, you can see full code here, but the gist is:

let prevBrowserWidth

//re-renders only if container size changed, good place to debounce
let renderApp = function() {
  const browserWidth = window.document.body.offsetWidth

  //saves re-render if nothing changed
  if (browserWidth === prevBrowserWidth) {
    return
  }

  prevBrowserWidth = browserWidth

  render(<App browserWidth={browserWidth} />, document.getElementById('root'))
}

//subscribing to resize event
window.addEventListener('resize', renderApp)

It obviously works without Redux (while I still use Redux) and I figured it would be as easy to do same with Redux. The advantage of this solution, compared to one with a component is that your react components stay completely agnostic of this and work with browser width as with any other props passed down. So it's a localized place to handle a side-effect. The disadvantage is that it only gives you a property and not event itself, so you can't really rely on it to trigger something that is outside of render function.


Besides that you can workaround you server-side rendering issue by using something like:

import ExecutionEnvironment from 'exenv'

  //...
  
componentWillMount() {
    if (ExecutionEnvironment.canUseDOM) {
      window.addEventListener(...);
    }
  }

1 Comment

Thank you for your solution. Accepted Ben's solution in order to increase his reputation (far less than yours) as both solutions were valid.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.