The Provider and Higher-Order Component patterns with React

bloodyowl
bloodyowl
Oct 30, 2015 · 4 min read

The provider pattern

Lots of React libraries need to make their data pass through all your component tree. For instance, Redux needs to pass its store and React Router needs to pass the current location. This could possibly seem to be handled using shared mutable state, but it only works on the client, when you have one state. If you prerender on the server, it’s just impossible to rely on such implementation.

Fortunately, React provides a way to pass data from top to bottom: context. You can basically see it as the global object of your component tree.

At the top of your app, you must therefore have a Provider. Its only role will basically be to add the data you want to the tree’s context, so that all its descendants can have access to it.

Let’s illustrate this pattern using a theme use-case: There are custom theme information you need to pass everywhere in your app.

import React, { Component, PropTypes, Children } from “react”class ThemeProvider extends Component {
static propTypes = {
theme: PropTypes.object.isRequired,
}
// you must specify what you’re adding to the context
static childContextTypes = {
theme: PropTypes.object.isRequired,
}
getChildContext() {
const { theme } = this.props
return { theme }
}
render() {
// `Children.only` enables us not to add a <div /> for nothing
return Children.only(this.props.children)
}
}
export default ThemeProvider

With this Provider, you are now able to pass the theme to any component who requires it.

import React from “react”
import { render } from “react-dom”
import ThemeProvider from “ThemeProvider”
import App from “App”
const mountNode = document.querySelector(“#App”)const theme = {
color: “#cc3300”,
fontFamily: “Georgia”,
}
render(
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>,
mountNode
)

Now that our theme is added to the context, we need a simple way for the components to grab it. Which leads us to the second pattern.

The Higher-Order Component pattern

We could say that any component that wants theme should declare a contextTypes static property, but it would be a bad idea for two reasons:

  • Maintainability: if at some point, there is a need for refactoring, having all these contextTypes spread around your repository could hurt really badly. Also, you wouldn’t be able to easily introduce a deprecation note for it once you planned to ditch something.

A second possible solution would be inheritance. This doesn’t work for two reasons:

  • More than one inheritance level is usually a bad idea. It often leads to colliding methods and requires you to check every class that it extended when you modify it. The previous mixins API with React.classClass merged methods which fixed this issue, but wasn’t practical at all if you wanted to figure out what was happening.

Therefore, the best way to create a reusable functionality is a Higher-Order Component. This means that we basically wrap the component in another one, whose only role is to grab the functionality and pass it as props. The component you export from your module is the Higher-Order Component, which renders yours.

In fact, you can see it as an intermediary point in your app which injects a few props that were in context. There are lots of advantages with this approach.

- Isolation: There is no risk of method or property collision in your class.
- Interoperability: It works with any React Component, no matter how it was defined.
- Maintainability: The wrapper component will only have one functionality, which makes it easier to reason about.

const React, { Component, PropTypes } from “react”const theme = (ComponentToWrap) => {
return class ThemeComponent extends Component {
// let’s define what’s needed from the `context`
static contextTypes = {
theme: PropTypes.object.isRequired,
}
render() {
const { theme } = this.context
// what we do is basically rendering `ComponentToWrap`
// with an added `theme` prop, like a hook
return (
<ComponentToWrap {…this.props} theme={theme} />
)
}
}
}
export default theme

Therefore, we can use the theme function to wrap any kind of component:

stateless functions

import React from “React”
import theme from “theme”
const MyStatelessComponent = ({ text, theme }) => (
<div style={{ color: theme.color }}>
{text}
</div>
)
export default theme(MyStatelessComponent)

classes

import React, { Component } from “React”
import theme from “theme”
// using `theme` as a ES7 decorator
@theme
class MyComponent extends Component {
render() {
const { text, theme } = this.props
return (
<div style={{ color: theme.color }}>
{text}
</div>
)
}
}
export default MyStatelessComponent// or just calling itimport React, { Component } from “React”
import theme from “theme”
class MyComponent extends Component {
render() {
const { text, theme } = this.props
return (
<div style={{ color: theme.color }}>
{text}
</div>
)
}
}
export default theme(MyStatelessComponent)

As theme is just a function, you can also make it take options using a simple closure:

const theme = (mergeProps = defaultMergeProps) =>
(ComponentToWrap) => {
// …
render() {
const { theme } = this.context
const props = mergeProps(this.props, { theme })
return (
<ComponentToWrap {…props} />
)
}
// …
}

and use it this way:

// …
const mergeProps = ((ownProps, themeProps) =>
({…themeProps, …ownProps})
export default theme(mergeProps)(MyComponent)

Cheers.

bloodyowl

Written by

bloodyowl

front-end developer