Last active
March 31, 2017 20:53
-
-
Save trotzig/56d1232ff48c14514371934d72fd59dd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { addEventListener, removeEventListener } from 'consolidated-events'; | |
import React, { PureComponent } from 'react'; | |
/** | |
* HoC that injects a `contextWidth` prop to the component, equal to the | |
* available width in the current context | |
* | |
* @param {Object} Component | |
* @return {Object} a wrapped Component | |
*/ | |
export default function withContextWidth(Component) { | |
return class extends PureComponent { | |
constructor() { | |
super(); | |
const initialState = { | |
contextWidth: undefined, | |
}; | |
this.state = initialState; | |
this._handleDivRef = this._handleDivRef.bind(this); | |
this._resizeHandle = addEventListener( | |
window, | |
'resize', | |
() => this.setState(initialState), | |
{ passive: true }, | |
); | |
} | |
componentWillUnmount() { | |
removeEventListener(this._resizeHandle); | |
} | |
_handleDivRef(domElement) { | |
if (!domElement) { | |
return; | |
} | |
this.setState({ | |
contextWidth: domElement.offsetWidth, | |
}); | |
} | |
render() { | |
if (typeof this.state.contextWidth === 'undefined') { | |
// This div will live in the document for a brief moment, just long | |
// enough for it to mount. We then use it to calculate its width, and | |
// replace it immediately with the underlying component. | |
return ( | |
<div | |
style={{ flexGrow: '1' }} | |
ref={this._handleDivRef} | |
/> | |
); | |
} | |
return ( | |
<Component | |
contextWidth={this.state.contextWidth} | |
{...this.props} | |
/> | |
); | |
} | |
}; | |
} |
Don't let the flexGrow: '1'
alarm you. The HOC isn't depending on being in a flex container. The flexGrow
is there to make sure that if it happens to render in a flexbox parent, it grows as much as it can.
@lencioni just pointed me at http://elementqueries.com/, which is basically what I'm trying to do here.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Truly responsive React components
Using media queries to style components differently depending on the screen width is great if you're only working in a single column. But let's say you have a multi-column layout where you want responsive components based on the available width in the current container? Or you want a component to be able to render in a lot of different contexts, with unknown widths? With regular media-queries, you can't do that.
withContextWidth
is a HOC that will inject acontextWidth
prop to the wrapped component. It will allow you to write components that render differently based on the currently available width. Here's an example -- aToggleButton
that collapses to a checkbox in narrow contexts.What's great here is that we can reuse this component in many contexts. If it's rendered in a table for instance, it's likely to render as a checkbox. But if it's a standalone component in a wide container, it's probably going to show the regular, wider version.
How does it work?
To figure out the available width in the current context, we drop an empty
<div>
in the DOM for a brief moment. As soon as the div is mounted, we measure its width, then re-render with the calculated width injected ascontextWidth
to the component. The component can then render things conditionally based on this number.I thought about ways to avoid the empty div but frankly couldn't come up with one that didn't have worse complexity and/or side-effects than the current approach. Let me know if you have ideas!
Limitations
This is still in an early exploration phase, so I expect more limitations/bugs to pop up later on. So far, I've only found issue:
Did you find this useful? Drop a comment and let me know what you think!