Media queries are classy! They are a basic building block of responsive web applications. You can do a lot with them: adapt the sizes, change a basic layout, show/hide some elements with CSS.

Sometimes, however, you kind of need more. Sometimes you want to use an entirely different layout for mobile devices.

Think an app that needs to have three columns at the same time on the desktop, and tabs on mobile devices.

That would be a helluva “fun” to manage with media queries alone: there are entirely different components at play. A grid on the desktop, and tabs on the phone.

What do we even do about it?..

Let’s imagine we already knew which layout to render. What would the render() method of the top-level component look like?

It would look like an if-else!

render () { const isMobile = ( magic ); if ( isMobile ) { return ( < Tabs > < Tab >< Yellow /></ Tab > < Tab >< Green /></ Tab > < Tab >< Purple /></ Tab > </ Tabs > ); } else { return ( < Columns > < Yellow /> < Green /> < Purple /> </ Columns > ); } }

Nothing too fancy, but that’s all we need!

Now comes the fun part. How do we determine which layout to render?

window.innerWidth

We could query window.innerWidth in render , and see if the screen is small enough to display the tabs.

const isMobile = window . innerWidth <= 500 ;

That looks simple! Unfortunately, there is an issue with the code above. It assumes window width to be static — get it once and render the appropriate layout. The reality of the web is different, of course.

If you resize your browser, the app won’t re-render, because it doesn’t know something has changed.

And yet, something clearly did change.

But how would we know?

resize events

The only way the dimensions of the page change is if a user resizes the browser (or rotates their phone.) Our browsers are kind enough to give us a simple event whenever the size of the page changes.

window . addEventListener ( 'resize' , someHandlerHere );

From there, we have two options:

call forceUpdate in the handler, forcing React to re-render the component, which will re-evaluate window.innerWidth in render , or

in the handler, forcing React to re-render the component, which will re-evaluate in , or store the window width in state, and have render use that.

forceUpdate is usually a sign that our data model isn’t quite right. The latter, however, is perfectly in line with React’s dom = render(state, props) equation.

Without further ado:

constructor () { super (); this . state = { width : window . innerWidth , }; } componentWillMount () { window . addEventListener ( 'resize' , this . handleWindowSizeChange ); } // make sure to remove the listener // when the component is not mounted anymore componentWillUnmount () { window . removeEventListener ( 'resize' , this . handleWindowSizeChange ); } handleWindowSizeChange = () => { this . setState ({ width : window . innerWidth }); }; render () { const { width } = this . state ; const isMobile = width <= 500 ; // the rest is the same... if ( isMobile ) { return ( < Tabs > < Tab >< Yellow /></ Tab > < Tab >< Green /></ Tab > < Tab >< Purple /></ Tab > </ Tabs > ); } else { return ( < Columns > < Yellow /> < Green /> < Purple /> </ Columns > ); } }

Nice!

Now render is simply a function of state , and we use the event listener to update our state.

Going forward