Created
November 22, 2015 18:37
-
-
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 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
/* | |
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