Our current compound component implementation is great, but it's limited in that users cannot render the structure they need. Let's allow the user to have more flexibility by using React context to share the implicit state to our child <Toggle/>
components. We will walk through using React's official context API with React.createContext
and use the given Provider
and Consumer
components to share state implicitly between our compound components giving our users the flexibility they need out of our component.
When we use compound component, we are also using React.Children.map:
return React.Children.map(this.props.children, child => { return React.cloneElement(child, { on: this.state.on, toggle: this.toggle, }) })
React.Children.map + this.props.children limits what compound compoment should found some structure, you can do:
return ( <Toggle onToggle={onToggle}> <Toggle.On>The button is on</Toggle.On> <Toggle.Button /> <Toggle.Off>The button is off</Toggle.Off> </Toggle> )
But app will break if you do:
return ( <Toggle onToggle={onToggle}> <Toggle.On>The button is on</Toggle.On> <Toggle.Button /> <div> <Toggle.Off>The button is off</Toggle.Off> </div> </Toggle> )
Because this.props.children will looking for direct child inside <Toggle>, if we add <div> wrapper, it will break the structure, then code won't work.
So we need more flexable code, to achieve that we can use Context.
// Flexible Compound Components with context import React from 'react' import {Switch} from '../switch' const ToggleContext = React.createContext() class Toggle extends React.Component { static On = ({children}) => ( <ToggleContext.Consumer> {contextValue => (contextValue.on ? children : null)} </ToggleContext.Consumer> ) static Off = ({children}) => ( <ToggleContext.Consumer> {contextValue => (contextValue.on ? null : children)} </ToggleContext.Consumer> ) static Button = props => ( <ToggleContext.Consumer> {contextValue => ( <Switch on={contextValue.on} onClick={contextValue.toggle} {...props} /> )} </ToggleContext.Consumer> ) state = {on: false} toggle = () => this.setState( ({on}) => ({on: !on}), () => this.props.onToggle(this.state.on), ) render() { return ( <ToggleContext.Provider value={{ on: this.state.on, toggle: this.toggle, }} > {this.props.children} </ToggleContext.Provider> ) } } function Usage({ onToggle = (...args) => console.log('onToggle', ...args), }) { return ( <Toggle onToggle={onToggle}> <Toggle.On>The button is on</Toggle.On> <Toggle.Off>The button is off</Toggle.Off> <div> <Toggle.Button /> </div> </Toggle> ) } Usage.title = 'Flexible Compound Components' export {Toggle, Usage as default}