React component with Media queries for Responsive

August 11, 2016

This is a very simple approach using matchMedia support. We store media queries and a listener function as an object in an array. When the media query returns true the listener function will set the relevant component state true.

In our actual project, this approach is used with Redux. The queries instead of setting component state, update the Store. Containers connected to this store send the breakPoint, orientation, and other props to each child component.

Just an experiment. If pursued further, would probably make it a HOC (Higher Order Component).

Gist available here.

Demo
If packed and loaded into a web page as we resize the browser we should see:

Change size of browser to test

state: { 
    "orientation": "landscape", 
    "breakPoint": "LG" 
}

Why?
While CSS media queries are very useful, sometimes being able to respond ‘behaviorally’ to media events is very powerful.

Tested on
Chrome, FireFox, IE, and Edge. I guess they all have JavaScript MediaQuery support.

How it works

In the life cycle method ‘componentDidMount’ we add a query to an array in the component:

this.mediaQueryFactory(obj, '(orientation: portrait)', this.onPortrait);

this.mediaQueryLists.push({
	mql: obj.mql,
	listener: this.onPortrait
});

the onPortrait is the listener function:

onPortrait : function(flag){
  if(flag){
	this.setState({orientation: 'portrait'});
  }
},

Full Code


import React from 'react';
import {render} from 'react-dom';
import "babel-polyfill";
import invariant from "invariant";
/**
* A P P
*
*/
var App = React.createClass({
/**
* Array of objects to contain the mediaQueryLists objects
*
* Each object is composed of:
* {
* mql: mediaQueryList,
* listener: function
} }
*
*/
mediaQueryLists : [],
/**
* Simple demo, dumps State to UI.
*/
render: function(){
return(
<div id="main" style={{padding:'4em'}}>
<h1 className="disclaimer">Change size of browser to test</h1>
<h2>{`state: ${JSON.stringify(this.state,null,2)}`}</h2>
</div>
);
},
/**
*
*/
componentDidMount : function(){
var obj = {}; // obj to use for passing mql to List of mql.
// TODO: these CSS strings should come from properties
this.mediaQueryFactory(obj, '(orientation: portrait)', this.onPortrait);
this.mediaQueryLists.push({
mql: obj.mql,
listener: this.onPortrait
});
this.mediaQueryFactory(obj, '(orientation: landscape)', this.onLandscape);
this.mediaQueryLists.push({
mql: obj.mql,
listener: this.onLandscape
});
this.mediaQueryFactory(obj, '(max-width: 767px)', this.onXS);
this.mediaQueryLists.push({
mql: obj.mql,
listener: this.onXS
});
this.mediaQueryFactory(obj, '(min-width:768px) and (max-width: 992px)', this.onSM);
this.mediaQueryLists.push({
mql: obj.mql,
listener: this.onSM
});
this.mediaQueryFactory(obj, '(min-width:993px) and (max-width: 1199px)', this.onMD);
this.mediaQueryLists.push({
mql: obj.mql,
listener: this.onMD
});
this.mediaQueryFactory(obj, 'screen and (min-width:1200px)', this.onLG);
this.mediaQueryLists.push({
mql: obj.mql,
listener: this.onLG
});
},
/**
*
*/
componentWillUnMount : function(){
this.mediaQueryLists.forEach( (mql) => {
mql.mql.removeListener(mql.listener);
});
this.mediaQueryLists.length = 0; // clear the array.
},
/**
* Responsive media support
*
* Create mediaQueryList object.
*
* See http://devdocs.io/dom/window/matchmedia
* See https://github.com/weblinc/media-match
*
* @param obj – obj to store the new mql
* @param String query – the CSS query
* @param func – the callBack function
* @return nothing
*/
mediaQueryFactory : function(obj, query, func){
var mql = window.matchMedia(query);
var listener = function(x){
func(x.matches);
}
mql.addListener(listener);
listener(mql);
obj.mql = mql;
},
/**
*/
onPortrait : function(flag){
if(flag){
this.setState({orientation: 'portrait'});
}
},
/**
*/
onLandscape : function(flag){
if(flag){
this.setState({orientation: 'landscape'});
}
},
/**
*/
onXS : function(flag){
if(flag){
this.setState({breakPoint:'XS'});
}
},
/**
*/
onSM : function(flag){
if(flag){
this.setState({breakPoint:'SM'});
}
},
/**
*/
onMD : function(flag){
if(flag){
this.setState({breakPoint:'MD'});
}
},
/**
*/
onLG : function(flag){
if(flag){
this.setState({breakPoint:'LG'});
}
},
});
/*
* Render the app on the page.
*/
render(<App />, document.getElementById('root'));
// end of index.js

view raw

index.js

hosted with ❤ by GitHub

Other MediaQuery React implementations

Addendum

{
  "name": "ReactMediaQuery",
  "version": "1.0.0",
  "description": "Attempt at using Media Queries in React",
  "main": "./index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "start": "webpack index.js dist/react-media-query-bundle.js"
  },
  "keywords": [
    "react"
  ],
  "author": "JosefBetancourt",
  "license": "SEE LICENSE IN COPYWRITE.TXT",
  "dependencies": {
    "babel-polyfill": "^6.9.1",
    "classnames": "^2.2.5",
    "react": "^15.3.0",
    "react-dom": "^15.3.0"
  },
  "devDependencies": {
    "babel-core": "^6.10.4",
    "babel-loader": "^6.2.4",
    "babel-plugin-add-module-exports": "^0.2.1",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-es2016": "^6.11.3",
    "babel-preset-react": "^6.3.13",
    "babel-preset-stage-2": "^6.11.0",
    "eslint": "^3.2.2",
    "webpack": "^1.13.1"
  }
}

package.json

var path = require('path');
const webpack = require('webpack');

module.exports = {
  /* entry point */
  entry: './index.js',
  /* output folder */
  output: {
    filename: 'dist/react-media-query-bundle.js'
  },
  devtool: 'source-map',
  module: {
    loaders: [
      {
        test: /.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react', 'stage-2']
        }
      },
    ]
  },
  eslint: {
    configFile: path.join(__dirname, 'eslint.js'),
    useEslintrc: false
  },  
  plugins: [
  ],
  resolve: {
    extensions: ['', '.js', '.json'] 
  },
  resolveLoader: {
      modulesDirectories: [
          'node_modules'
      ]
  }
};

webpack.config.js

<!DOCTYPE html>
<html>
  <head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">  
  	<title>React Media Queries</title>
  </head>
  <body class="body">
    <div id="root" class="">Loading page, please wait ....</div>
	<script src="dist/react-media-query-bundle.js"></script>
  </body>
</html>

index.html