Skip to content

Instantly share code, notes, and snippets.

@mg
Created November 22, 2015 18:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mg/f3e865f88dec64fd6aff to your computer and use it in GitHub Desktop.
Save mg/f3e865f88dec64fd6aff to your computer and use it in GitHub Desktop.
Describing a pattern I use to leverage the benefits context in React.js but still keeping my components simple
/*
This pattern is useful when using context in React to pass values down
the component tree. The example I use here is localization but I use it every
time I use contexts in React
*/
/*
Locale is a Higher Order Component that creates the "locale" context. Components
that are locale sensitive can use this context value to both get the active
locale and to be notified when the locale changes. It also puts the function
"setLocale" in the context.
Use it to decorate the root component in your component tree.
e.g.
export default Locale(MyRootComponent)
MyRootComponent now accepts prop locale to set locale at mount time
or simply use the default value (en)
*/
export function Locale(Component) {
return class extends React.Component {
static propTypes= {
locale: React.PropTypes.string,
}
static defaultProps= {
locale: 'en',
}
render() {
return <Component {...this.props}/>
}
static childContextTypes = {
locale: React.PropTypes.string,
setLocale: React.PropTypes.func,
}
getChildContext() {
return {
locale: this.state.locale,
setLocale: locale => this.setState({locale})
}
}
constructor(props) {
super(props)
this.state= {
locale: this.props.locale,
}
}
}
}
/*
SetLocale is a Higher Order Component that exposes the function
"setLocale" on the component that it wraps. This function can be used
to change the current locale. It also puts the current "locale" value
into props. This so we can e.g. generate a list of radio buttons of
supported locales and select the one that is currently active.
*/
export function SetLocale(Component) {
return class extends React.Component {
render() {
return <Component {...this.props} locale={this.context.locale} setLocale={this.context.setLocale}/>
}
static contextTypes= {
locale: React.PropTypes.string,
setLocale: React.PropTypes.func,
}
}
}
/*
Localize is a Higher Order Component that exposes current locale as a prop
for the wrapped component. This is useful for the following:
1. The component has access to the current locale in this.props.locale
2. Using a HOC that rerenders when props change, we can make this rerendering
sensitive to locale changes
3. Using props over context makes it easier to unit test the component, no need
to bootstrap an enviroment with locale in context, simply pass the locale value
in the test as props.
*/
export function Localize(Component) {
return class extends React.Component {
render() {
return <Component {...this.props} locale={this.context.locale}/>
}
static contextTypes= {
locale: React.PropTypes.string,
}
}
}
/*
Example of a localized component
*/
const FormatNumber= ({value, locale}) => {
// use some way to format value according to locale
return <span>{formattedValue}</span>
}
export { FormatNumber } // export dumb component for unit testing
export default Localized(FormatNumber) // export locale sensitive component
// if we have RenderOnProps HOC that implements
// shouldComponentUpdate in terms of comparing props
// we can make it sensitive to changes in locale
export default Localized(RerenderOnProps(FormatNumber))
/*
So the general pattern is:
1. A HOC that accepts a value on props and pushes it to context
2. A HOC that reads a value from a context and pushes it to a component via props
3. Return two components from component.jsx:
- a dumb named component to use in unit tests
- a smart default component to use in other components
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment