Javascript based media queries using React, Redux, and window.matchmedia

During a recent experiment, I thought it would be interesting to duplicate some of the css media query functionality inside of the jsx environment of our react stack. JSX actually lends itself quite well to this approach. React already opened the floodgates to combining our presentation layer with our logic layer. I theorized that it might be useful to pull much of our responsive work into the view as well.

This lead us down the path of recreating media queries as a series of javascript functions, allowing us to programmatically serve unique classes, components, and inline styles based on the viewports breakpoint state.

We had played with simpler versions of this in the past, namely attaching handlers to the window.resize event that check when the viewport size crossed our breakpoint thresholds, but it proved clunky and hard to manage, requiring a lot of boilerplate when you wanted to watch more than one or two breakpoints.

While something like this is workable:

It’s not very versatile, and can quickly become a nightmare when building a large scale web app with a dozen different breakpoints.

Luckily, a few tools became available that made this kind’ve work a lot more viable, so we went about developing a pattern that would be easy to use for small and large scale web apps alike. It made sense to integrate this pattern into our normal stack. Namely React and Redux.

The problem: Serve dynamic content / layouts based on viewport size, without having to resort to assigning unique one-off classes to elements, as is typical in responsive work.

The solution: Using Redux and window.matchmedia, we reproduce CSS’s basic media query functionality in javascript, allowing us to serve unique classes, components, and inline styles that swap as the breakpoints change. This allows us to create hyper modular — higher level css rules without having to account for a lot of the one-off styles typically necessary in responsive work.

We simply serve the appropriate classes for the matched media query. This allows us to write one set of modular classes, rather than a unique class per element that specifies all the css rules for each breakpoint.

For example, instead of something like this:

We can write something like this:

I’ll discuss the syntax further along, suffice it to say we’re now using a new setClass function to serve a different class based on the currently matched breakpoint. The compiled result that shows up in the DOM will be as such:

The main difference is that instead of creating a single .heading class that can only be used for headings, we create 3 modular classes that can be used anywhere on any element, and we control where and when they show up using a reproduction of the css media query functionality in our javascript. The media queries are successfully offloaded from the css file into the jsx file. This allows us to drastically reduce the size of our css files, as well as craft a more modular rather than element specific stylesheet.

How it works

1. Create the AppWrapper

Firstly, you’ll need an AppWrapper component. The only requirement for this component is that it mounts at the start of your app, and stays mounted until your app is closed. This component will house the event handler for your changing breakpoints, and will also communicate with your store to report the active breakpoints on initial mount as well as updating the store with the new breakpoint value as the window is resized

Create a new component called AppWrapper.jsx. It can start out fairly simple. Right now, just a basic component that returns its children wrapped in a div. It uses redux’s connect function to connect to the store. This will allow us to use this.props.dispatch in a later step.

2. Create the responsiveHelpers file

Next, create a simple utils file called responsiveHelpers.js and save it in a new utils folder. This is where we're going to house a few helper functions, as well as define our breakpoints. You can define whichever breakpoints you prefer. Just make sure to list them from largest to smallest (this is assuming a desktop first responsive system).

3. Setting up our event handler

Return to the AppWrapper file. We’re going to add our event handler and dispatch our actions.

4. Creating the action & reducer

Add the setActiveBreakpoint action to your actions file:

Next, add the breakpoint reducer to your reducers file:

5. Creating the helper functions

Return to the utils/responsiveHelpers.js file. We are now going to create a few helper functions that will allow us to interface with our new breakpoint system. First up, the setClass function that we used in the initial example. This function will accept two parameters, the first of which, classObj, is an object with key value pairs describing which strings should be returned for which matched breakpoints. The second parameter, breakpoint, will be the breakpoint object from the store that we dealt with in the previous section.

Next, we are going to add two additional helper functions: breakpointIsGreaterThan and breakpointIsLessThan. These functions return a boolean and can be used inside ternery operators to conditionally load or hide components and jsx elements.

Firstly, let’s add a helper that will make these functions easier to use. This function simply allows us to use a string to represent the breakpoint parameter and queries it against the breakpoints object we created in step 2.

Now, the two helper functions:

5. Putting it all together

We are now ready to start using our new breakpoint system in our components. A bit of boilerplate needs to be added to any component that wants to make use the breakpoint system.

  1. The component must subscribe to the breakpoint object in the store.
  2. The utils functions need to be imported from responsiveHelpers

The setClass function is commonly used to return string classes, but can also be used anywhere else a string can be served. For example, another common use case is to responsively serve inline styles:

The breakpointIsGreaterThan and breakpointIsLessThan functions are commonly used in conjunction with ternery operators, as well as within if / else statements.

That’s pretty much it. These 3 functions are pretty versatile and should allow us to recreate much of the functionality of traditional css media queries.

6. Browser support

Browser support is pretty good. The only aspect that is of any concern is the use of the window.matchMedia API, which is supported in all modern browsers, going as far back as IE10.

IE9 and below will need an alternate solution. It is possible to attach the event handler to the window.resize event, in which case a generous debounce should be used to cut down on the amount of times the event is called during a resize.

I’m not recommending migrating 100% of your media queries into your JSX files. In our client projects, we have used this pattern to supplement traditional media queries, not replace them outright. It’s about giving us options, and in the end it has resulted in much smaller, easier to manage CSS files.