Coder Social home page Coder Social logo

compound-react-components's Introduction

Compound React Components

The Compound Components Pattern enables you to provide a set of components that implicitly share state for a simple yet powerful declarative API for reusable components.

Compound components are components that work together to form a complete UI. The classic example of this is <select> and <option> in HTML.

~Kent C Dodds

React properties and methods

  • React.Children.map()
  • React.cloneElement()

The Pattern

Pass in the children components as props to the parent component. e.g.

App.js

import { ToggleProvider, ToggleOn, ToggleOff, ToggleButton } from "./components/Toggle"

function App() {
  return (
      <ToggleProvider>
        <ToggleOn>The button is on</ToggleOn>
        <ToggleOff>The button is off</ToggleOff>
        <div>Normal HMTL element</div>
        <div>
          <ToggleButton />
        </div>
      </ToggleProvider>
  )
}
  
export default App

The user of the compound component does not ever interact with the internal state.

/context/index.js

const ToggleContext = ()=>{
  const context = React.createContext(false)
  context.displayName = "ToggleContext" //Setting the displayName allows it to be differentially displayed in React DevTools as ToggleContext.Provider and ToggleContext.Provider rather than the generic Context.Provider and Context.Consumer
  return context
}

export { ToggleContext }

/hooks/index.js

import React from "react"
import { ToggleContext } from "./context/"

function useToggle() {
  const context = React.useContext(ToggleContext)
  if (!context) { //Provides the user with a useful error message if they try to use any of the Toggle components outside ofr Toggle.Provider
    throw new Error('useToggle must be utilized with Toggle')
  }
  return context
}

export { useToggle }

The parent component returns cloned children components with the implicit state and methods passed in as props.

Toggle.js

import React from "react"
import { ToggleContext } from "./context/"
import { useToggle } from "./hooks/"
import {Switch} from "./components/switch" //Component displaying the switch button

function ToggleProvider(props) {
  const [on, setOn] = React.useState(false)
  const toggle = () => setOn(!on)

  return (
    <ToggleContext.Provider value={{on, toggle}}>
      {React.Children.map(children, child => {
        return typeof child.type === 'string' //skip html elements
          ? child
          : React.cloneElement(child, {on, toggle})
      })}
    </ToggleContext.Provider>
  )
}

function ToggleOn({children}) {
  const {on} = useToggle()
  return on ? children : null
}

function ToggleOff({children}) {
  const {on} = useToggle()
  return on ? null : children
}

function ToggleButton({...props}) {
  const {on, toggle} = useToggle()
  return <Switch on={on} onClick={toggle} {...props} />
}

export { ToggleProvider, ToggleButton, ToggleOn, ToggleOff }

Important: An error will display in the console if there is a child component that cannot accept the props e.g. a DOM component. To mitigate against this the the React.Children.map() needs to include an if statement.

This allows custom components to be passed in as children and these will receive the implicit state etc. as props.

Example

const MyCustomComponent = (on, toggle) => on? "I say the button is on" : "I say the button is off"
<Toggle>
  //Children components passed in as props
  <ToggleOn>The button is on</ToggleOn>
  <ToggleOff>The button is off</ToggleOff>
  <ToggleButton />
  <MyCustomComponent /> //This custom component receives the implicit state of the ToggleProvider
</Toggle>

Alternatively, you can create an array of allowed child types that will receive the internal props.

...

function Toggle(props) {
  const [on, setOn] = React.useState(false)
  const toggle = () => setOn(!on)
  const allowedTypes = ['ToggleOn', 'ToggleOff', 'ToggleButton'] //Array of permitted element types
  return  (
    <ToggleContext.Provider value={{on, toggle}}>
      {React.Children.map(props.children, child => {
        if(allowedTypes.includes(child.type.name) return React.cloneElement(child, {on, toggle})
        return child
      })}
    </ToggleContext.Provider>
  )
}

...

Attribution: https://epicreact.dev/modules/advance-react-patterns/

compound-react-components's People

Contributors

lesansley avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.