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.
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.
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
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
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:
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.
- The component must subscribe to the breakpoint object in the store.
- The utils functions need to be imported from responsiveHelpers
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:
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.