Skip to content

Instantly share code, notes, and snippets.

@jkneal
Created March 9, 2015 13:36
Show Gist options
  • Save jkneal/23aee729dd20410af7a6 to your computer and use it in GitHub Desktop.
Save jkneal/23aee729dd20410af7a6 to your computer and use it in GitHub Desktop.
React Element Query
const React = require('react')
const {ElementQueryMixin} = require('../helpers/element-query')
require('./card.styl')
module.exports = {
title: 'Element Queries',
component: React.createClass({
displayName: 'ElementQueryDemo',
render() {
return (<div style={{marginBottom: 50}}>
<ElementQueryStyleDemo/>
<ElementQueryTableDemo/>
<ElementQueryCardDemo/>
</div>
)
}
})
}
const ElementQueryStyleDemo = React.createClass({
displayName: 'ElementQueryStyleDemo',
render() {
return (<div>
<h2>Changing Styles</h2>
<p>Colors change ever 200px below 900px</p>
<ColorBar/>
<p>Color bar in 400px container</p>
<div style={{width: 400}}>
<ColorBar/>
</div>
</div>
)
}
})
const ColorBar = React.createClass({
displayName: 'ColorBar',
mixins: [ElementQueryMixin],
render() {
let color = 'blue'
if (this.matchMedia('(min-width: 701px) and (max-width: 900px)')) {
color = 'red'
}
else if (this.matchMedia('(min-width: 501px) and (max-width: 700px)')) {
color = 'green'
}
else if (this.matchMedia('(min-width: 301px) and (max-width: 500px)')) {
color = 'orange'
}
const style = {
width: '100%',
height: '100px',
color: 'white',
padding: 30,
backgroundColor: color
}
return (<div>
<div style={style}>Watch the colors!</div>
{this.getEQSensor()}
</div>
)
}
})
const ElementQueryTableDemo = React.createClass({
displayName: 'ElementQueryTableDemo',
mixins: [ElementQueryMixin],
render() {
return (<div>
<h2>Responsive Table</h2>
<p>Break point at 900px</p>
{this.matchMedia('(min-width: 901px)') && <table>
<thead><th>Header 1</th><th>Header 2</th>
<th>Header 3</th></thead>
<tr><td>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</td>
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</td>
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</td>
</tr>
</table>}
{this.matchMedia('(max-width: 900px)') && <ul>
<li>Header 1 - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</li>
<li>Header 2 - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</li>
<li>Header 3 - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</li>
</ul>}
{this.getEQSensor()}
</div>
)
}
})
const ElementQueryCardDemo = React.createClass({
displayName: 'ElementQueryCardDemo',
render() {
return (<div>
<h2>Responsive Card</h2>
<p>Large image appears when card is larger than 400px</p>
<div style={{width: '50%', float: 'left', marginRight: 30}}>
<KittyCard/>
</div>
<div style={{width: '200px', float: 'right'}}>
<KittyCard/>
</div>
</div>
)
}
})
const KittyCard = React.createClass({
displayName: 'KittyCard',
mixins: [ElementQueryMixin],
render() {
return (<div className="card primary">
{this.matchMedia('(min-width: 400px)') &&
<img style={{width: '100%'}} src="http://placekitten.com/g/400/200"/>}
<div className="card-divider">
{this.matchMedia('(max-width: 399px)') &&
<img style={{width: 25, height: 15, marginRight: 10}} src="http://placekitten.com/g/400/200"/>}
I'm so cute!
</div>
<div className="card-section">
<h4>Look at This Swag Card</h4>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi iusto reprehenderit
voluptatem odio deleniti provident aliquam qui magnam aspernatur necessitatibus.</p>
</div>
{this.getEQSensor()}
</div>
)
}
})
const React = require('react')
const {forIn} = require('lodash')
/**
* Mixin which provides element queries for the component.
*
* Call matchMedia to run an element query. This will setup a listener that will update the component's
* state whenever the query result changes.
*
* @see http://www.backalleycoder.com/2014/04/18/element-queries-from-the-feet-up/
*/
export const ElementQueryMixin = {
getInitialState() {
return {
queries: [],
mqls: {}
}
},
matchMedia(query) {
if (this.state.mqls[query]) {
return this.state.mqls[query].matches
}
// since this can be called during render and we can't update state, need to
// run on next tick. Adding a little delay due to quirk with firefox
setTimeout(() => {
if (this.isMounted()) {
this.runQuery(query)
}
else {
// hold query for evaluation on component mount
let queries = this.state.queries
queries.push(query)
this.setState({queries: queries})
}
}, 10)
return false
},
runQuery(query) {
const probeNode = this.refs.sensor.getDOMNode()
if (probeNode.contentDocument) {
const window = probeNode.contentDocument.defaultView
const mql = window.matchMedia(query)
mql.addListener(this.handleMediaChange(query))
this.saveMediaQueryList(query, mql)
}
},
saveMediaQueryList(query, mql) {
let mqls = this.state.mqls
mqls[query] = mql
this.setState({mqls: mqls})
},
handleMediaChange(query) {
return (mql) => this.saveMediaQueryList(query, mql)
},
componentDidMount() {
this.state.queries.forEach(query => this.runQuery(query))
},
componentWillUnmount() {
forIn(this.state.mqls, (mql, query) => mql.removeListener(this.handleMediaChange(query)))
},
getEQSensor() {
return <object ref="sensor" style={{width: '100%', height: 0}} type="text/html" data="about:blank">
</object>
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment