[React] Prevent Unnecessary Rerenders of Compound Components using React Context

Due to the way that React Context Providers work, our current implementation re-renders all our compound component consumers with every render of the <Toggle /> which could lead to unnecessary re-renders. Let's fix that by ensuring that the value prop we pass to the <ToggleContext.Provider /> is only changed when the state changes.

 

// Flexible Compound Components with context
// This allows you to avoid unecessary rerenders

import React from 'react'
import {Switch} from '../switch'

const ToggleContext = React.createContext()

function ToggleConsumer(props) {
  return (
    <ToggleContext.Consumer {...props}>
      {context => {
        if (!context) {
          throw new Error(
            `Toggle compound components cannot be rendered outside the Toggle component`,
          )
        }
        return props.children(context)
      }}
    </ToggleContext.Consumer>
  )
}

class Toggle extends React.Component {
  static On = ({children}) => (
    <ToggleConsumer>
      {({on}) => (on ? children : null)}
    </ToggleConsumer>
  )
  static Off = ({children}) => (
    <ToggleConsumer>
      {({on}) => (on ? null : children)}
    </ToggleConsumer>
  )
  static Button = props => (
    <ToggleConsumer>
      {({on, toggle}) => (
        <Switch on={on} onClick={toggle} {...props} />
      )}
    </ToggleConsumer>
  )
  // The reason we had to move `toggle` above `state` is because
  // in our `state` initialization we're _using_ `this.toggle`. So
  // if `this.toggle` is not defined before state is initialized, then
  // `state.toggle` will be undefined.
  toggle = () =>
    this.setState(
      ({on}) => ({on: !on}),
      () => this.props.onToggle(this.state.on),
    )
  state = {on: false, toggle: this.toggle}
  render() {
    return (
      <ToggleContext.Provider value={this.state}>
        {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}

 

posted @ 2018-08-29 03:34  Zhentiw  阅读(491)  评论(0编辑  收藏  举报