This site runs best with JavaScript enabled.

Mixing Component Patterns

May 07, 2018


Let's make a component that supports Render Props, Component Injection, Compound Components, the Provider Pattern, and Higher Order...

Let's make a component that supports Render Props, Component Injection, Compound Components, the Provider Pattern, and Higher Order Components!

This last week I gave three workshops at Frontend Masters:

If you're a Frontend Masters subscriber you can watch the unedited version of these courses now. Edited courses should be available for these soon.

The Advanced React Patterns course went especially well. I want to take some of the things that I taught in that workshop and share it with you all + take a it a little further than I took it in the course.

I've created a CodeSandbox that I really suggest you spend a solid 10 minutes reading through. I added a TON of comments to the code to walk you through combining all of the following patterns in a single component:

  • Compound Components
  • Render Props
  • Component Injection
  • Provider Pattern
  • Higher Order Components

Here's the implementation without any comments just to spark your interest:

1import React from 'react'
2import {render} from 'react-dom'
3import hoistNonReactStatics from 'hoist-non-react-statics'
4import {Switch} from './switch'
5
6const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args))
7const ToggleContext = React.createContext({
8 on: false,
9 toggle: () => {},
10 getTogglerProps: props => props,
11})
12
13class Toggle extends React.Component {
14 static Consumer = props => (
15 <ToggleContext.Consumer {...props}>
16 {state => Toggle.getUI(props.children, state)}
17 </ToggleContext.Consumer>
18 )
19 static On = ({children}) => (
20 <Toggle.Consumer>{({on}) => (on ? children : null)}</Toggle.Consumer>
21 )
22 static Off = ({children}) => (
23 <Toggle.Consumer>{({on}) => (on ? null : children)}</Toggle.Consumer>
24 )
25 static Button = props => (
26 <Toggle.Consumer>
27 {({getTogglerProps}) => <Switch {...getTogglerProps(props)} />}
28 </Toggle.Consumer>
29 )
30 static getUI(children, state) {
31 let ui
32 if (Array.isArray(children) || React.isValidElement(children)) {
33 ui = children
34 } else if (children.prototype && children.prototype.isReactComponent) {
35 ui = React.createElement(children, state)
36 } else if (typeof children === 'function') {
37 ui = children(state)
38 } else {
39 throw new Error('Please use one of the supported APIs for children')
40 }
41 return ui
42 }
43 toggle = () =>
44 this.setState(
45 ({on}) => ({on: !on}),
46 () => this.props.onToggle(this.state.on),
47 )
48 getTogglerProps = ({onClick, ...props} = {}) => ({
49 onClick: callAll(onClick, this.toggle),
50 'aria-pressed': this.state.on,
51 ...props,
52 })
53 state = {
54 on: false,
55 toggle: this.toggle,
56 getTogglerProps: this.getTogglerProps,
57 }
58 render() {
59 const {children, ...rest} = this.props
60 return (
61 <ToggleContext.Provider value={this.state} {...rest}>
62 {Toggle.getUI(children, this.state)}
63 </ToggleContext.Provider>
64 )
65 }
66}
67Toggle.Consumer.displayName = 'Toggle.Consumer'
68Toggle.On.displayName = 'Toggle.On'
69Toggle.Off.displayName = 'Toggle.Off'
70Toggle.Button.displayName = 'Toggle.Button'
71
72function withToggle(Component) {
73 function Wrapper(props, ref) {
74 return (
75 <Toggle.Consumer>
76 {toggleState => <Component {...props} toggle={toggleState} ref={ref} />}
77 </Toggle.Consumer>
78 )
79 }
80 Wrapper.displayName = `withToggle(${Component.displayName || Component.name})`
81 const WrapperWithRef = React.forwardRef(Wrapper)
82 hoistNonReactStatics(WrapperWithRef, Component)
83 return WrapperWithRef
84}
85
86export {Toggle, withToggle}

That's pretty much it for the newsletter today actually. I spent a good chunk of time preparing that codesandbox so give it a good solid look!

✨ ✨

The idea isn't necessarily to encourage that every component be implemented like this one, but more to show how you could use these patterns together to make an extremely flexible API for situations where that's useful. If you are going to choose only one pattern, I recommend the render props pattern, because all the other patterns can be implemented on top of this one and it's the simplest from a consumer's point of view.

Enjoy the codesandbox. And good luck!

Subscribe now for more content like this directly in your inbox 2 weeks before it's published.

Learn more about React from me:

  • egghead.io (beginners) — My Beginner's Guide to React absolutely free on egghead.io.
  • egghead.io (advanced) — My Advanced React Component Patterns course!
  • Frontend Masters — My Advanced React Component Patterns course.
  • Workshop.me — I'm giving my Advanced Component Patterns workshop in person in Portland in July!
  • Workshop.me — I'm giving my Intro to React workshop in person in Salt Lake City in August!
  • Workshop.me — I'm giving my Advanced Component Patterns workshop in person in Salt Lake City in August!

Things to not miss:

Discuss on Twitter • Edit post on GitHub

Share article
loading relevant upcoming workshops...
Kent C. Dodds

Kent C. Dodds is a JavaScript software engineer and teacher. He's taught hundreds of thousands of people how to make the world a better place with quality software development tools and practices. He lives with his wife and four kids in Utah.