Unleash the power of the higher-order components

Unleash the power of the higher-order components

How to avoid unnecessary duplications in your React app? You want to make sure that your components are maintainable and reusable. You can achieve this by writing your code in a functional way using the higher-order components (HoCs). I want to show you how to define smart HoCs and use them to enhance presentational components. You'll also learn how to compose several HoCs to get more complex logic. Sticking to this pattern, you'll end up with reusable components that are readable and easy to test since each component is responsible only for a single task.

6b50fbb7ade3c6b576b2c2b8ed4b0f7d?s=128

David Kopal

July 12, 2018
Tweet

Transcript

  1. @coding_lawyer Unleash the power of the higher-order components David Kopal

  2. @coding_lawyer 
 @coding_lawyer codinglawyer.net

  3. @coding_lawyer lambdup.io

  4. @coding_lawyer Who programs in React?

  5. @coding_lawyer React HoCs Reusability Composition Recompose Introduction Conclusion

  6. @coding_lawyer React HoCs Reusability Composition Recompose Introduction Conclusion

  7. @coding_lawyer const SWChars = [ { name: 'Luke', side: 'light'

    }, { name: 'Darth Vader', side: 'dark' }, { name: 'Obi-wan Kenobi', side: 'light' }, { name: 'Palpatine', side: 'dark' } ] Render(<FilteredList list={SWChars} />)
  8. @coding_lawyer class FilteredList extends React.Component { constructor(props) { super(props) this.state

    = { side: 'dark' } } toggleState() { this.setState({ side: this.state.side === 'dark' ? 'light' : 'dark' }) } render() { const filtered = this.props.list.filter( char => char.side === this.state.side) return filtered.length > 1 ? ( <div> {filtered.map(char => ( <div key={char.name}> <div>Character: {char.name}</div> <div>Side: {char.side}</div> </div> ))} <button onClick={() => this.toggleState()}>Toggle</button> </div> ) : (<div>You need to have more than one character.</div>)}}
  9. None
  10. @coding_lawyer class FilteredList extends React.Component { constructor(props) { super(props) this.state

    = { side: 'dark' } } toggleState() { this.setState({ side: this.state.side === 'dark' ? 'light' : 'dark' }) } render() { const filtered = this.props.list.filter( char => char.side === this.state.side) return filtered.length > 1 ? ( <div> {filtered.map(char => ( <div key={char.name}> <div>Character: {char.name}</div> <div>Side: {char.side}</div> </div> ))} <button onClick={() => this.toggleState()}>Toggle</button> </div> ) : (<div>You need to have more than one character.</div>)}}
  11. @coding_lawyer FilteredList (logic + presentation) not reusable

  12. @coding_lawyer class FilteredList extends React.Component { constructor(props) { super(props) this.state

    = { side: 'dark' } } toggleState() { this.setState({ side: this.state.side === 'dark' ? 'light' : 'dark' }) } render() { const filtered = this.props.list.filter( char => char.side === this.state.side) return filtered.length > 1 ? ( <div> {filtered.map(char => ( <div key={char.name}> <div>Character: {char.name}</div> <div>Side: {char.side}</div> </div> ))} <button onClick={() => this.toggleState()}>Toggle</button> </div> ) : (<div>You need to have more than one character.</div>)}}
  13. @coding_lawyer React HoCs Reusability Composition Recompose Introduction Conclusion

  14. @coding_lawyer smart / presentational component

  15. @coding_lawyer const SWChars = [ { name: 'Luke', side: 'light'

    }, { name: 'Darth Vader', side: 'dark' }, { name: 'Obi-wan Kenobi', side: 'light' }, { name: 'Palpatine', side: 'dark' } ] const DisplayList = ({ list }) => list.map(char => <div> {/* render UI */} </div>) Render(<DisplayList list={SWChars} />)
  16. @coding_lawyer const SWChars = [ { name: 'Luke', side: 'light'

    }, { name: 'Darth Vader', side: 'dark' }, { name: 'Obi-wan Kenobi', side: 'light' }, { name: 'Palpatine', side: 'dark' } ] const DisplayList = ({ list }) => list.map(char => <div> {/* render UI */} </div>) Render(<DisplayList list={SWChars} />) reusable
  17. @coding_lawyer DisplayList (presentation) reusable

  18. @coding_lawyer const FilteredList = ({ list, side }) => {

    const filteredList = list.filter(char => char.side === side) return filteredList.map(char => <div> {/* render UI */} </div>) } Render(<FilteredList side="dark" list={SWChars} />)
  19. @coding_lawyer const FilteredList = ({ list, side }) => {

    const filteredList = list.filter(char => char.side === side) return filteredList.map(char => <div> {/* render UI */} </div>) } Render(<FilteredList side="dark" list={SWChars} />) not reusable
  20. @coding_lawyer FilteredList (logic + presentation) not reusable

  21. @coding_lawyer const withFilterProps = BaseComponent => ({ list, side })

    => { const transformedProps = list.filter(char => char.side === side) return <BaseComponent list={transformedProps} /> } const DisplayList = ({ list }) => list.map(char => <div> {/* render UI */} </div>) const FilteredList = withFilterProps(DisplayList) Render(<FilteredList side="dark" list={SWChars} />)
  22. @coding_lawyer const withFilterProps = BaseComponent => ({ list, side })=>

    { const transformedProps = list.filter(char => char.side === side) return <BaseComponent list={transformedProps} /> } const FilteredList = withFilterProps(DisplayList)
  23. @coding_lawyer const withFilterProps = BaseComponent => ({ list, side })=>

    { const transformedProps = list.filter(char => char.side === side) return <BaseComponent list={transformedProps} /> } const FilteredList = withFilterProps(DisplayList) Higher-order component
  24. @coding_lawyer const HoC = BaseComponent => EnhancedComponent

  25. @coding_lawyer const withFilterProps = BaseComponent => ({ list, side })=>

    { const transformedProps = list.filter(char => char.side === side) return <BaseComponent list={transformedProps} /> } const FilteredList = withFilterProps(DisplayList) Higher-order component
  26. @coding_lawyer const withFilterProps = BaseComponent => ({ list, side })=>

    { const transformedProps = list.filter(char => char.side === side) return <BaseComponent list={transformedProps} /> } const FilteredList = withFilterProps(DisplayList) Higher-order component
  27. @coding_lawyer const withFilterProps = BaseComponent => ({ list, side })

    => { const transformedProps = list.filter(char => char.side === side) return <BaseComponent list={transformedProps} /> } const DisplayList = ({ list }) => list.map(char => <div> {/* render UI */} </div>) const FilteredList = withFilterProps(DisplayList) reusable
  28. @coding_lawyer DisplayList (presentation) withFilterProps (logic) reusable

  29. @coding_lawyer class FilteredList extends React.Component { constructor(props) { super(props) this.state

    = { side: 'dark' } } toggleState() { this.setState({ side: this.state.side === 'dark' ? 'light' : 'dark' }) } render() { const filtered = this.props.list.filter( char => char.side === this.state.side) return filtered.length > 1 ? ( <div> {filtered.map(char => ( <div key={char.name}> <div>Character: {char.name}</div> <div>Side: {char.side}</div> </div> ))} <button onClick={() => this.toggleState()}>Toggle</button> </div> ) : (<div>You need to have more than one character.</div>)}}
  30. None
  31. @coding_lawyer class FilteredList extends React.Component { constructor(props) { super(props) this.state

    = { side: 'dark' } } toggleState() { this.setState({ side: this.state.side === 'dark' ? 'light' : 'dark' }) } render() { const filtered = this.props.list.filter( char => char.side === this.state.side) return filtered.length > 1 ? ( <div> {filtered.map(char => ( <div key={char.name}> <div>Character: {char.name}</div> <div>Side: {char.side}</div> </div> ))} <button onClick={() => this.toggleState()}>Toggle</button> </div> ) : (<div>You need to have more than one character.</div>)}}
  32. None
  33. None
  34. @coding_lawyer React HoCs Reusability Composition Recompose Introduction Conclusion

  35. @coding_lawyer decoupling component from the data

  36. @coding_lawyer const withFilterProps = BaseComponent => ({ list, side })

    => { const transformedProps = list.filter(char => char.side === side) return <BaseComponent list={transformedProps} /> } const DisplayList = ({ list }) => list.map(char => <div> {/* render UI */} </div>) const FilteredList = withFilterProps(DisplayList) reusable
  37. @coding_lawyer const withFilterProps = BaseComponent => ({ list, side })

    => { const transformedProps = list.filter(char => char.side === side) return <BaseComponent list={transformedProps} /> }
  38. @coding_lawyer const withFilterProps = BaseComponent => ({ list, side })

    => { const transformedProps = list.filter(char => char.side === side) return <BaseComponent list={transformedProps} /> } not reusable
  39. @coding_lawyer DisplayList (presentation) withFilterProps (logic) not reusable reusable

  40. @coding_lawyer not reusable const withFilterProps = BaseComponent => ({ list,

    side }) => { const transformedProps = list.filter(char => char.side === side) return <BaseComponent list={transformedProps} /> } reusable const withTransformProps = transformFunc => BaseComponent => baseProps => { const transformedProps = transformFunc(baseProps) return <BaseComponent {...transformedProps} /> }
  41. @coding_lawyer const withTransformProps = transformFunc => BaseComponent => baseProps =>

    { const transformedProps = transformFunc(baseProps) return <BaseComponent {...transformedProps} /> } const DisplayList = ({ list }) => list.map(char => <div> {/* render UI */} </div>) const FilteredList = withTransformProps( ({ list, side }) => ({ list: list.filter(char => char.side === side) }))(DisplayList) Render(<FilteredList side="dark" list={SWChars} />)
  42. @coding_lawyer const withTransformProps = transformFunc => BaseComponent => baseProps =>

    { const transformedProps = transformFunc(baseProps) return <BaseComponent {...transformedProps} /> } const DisplayList = ({ list }) => list.map(char => <div> {/* render UI */} </div>) const FilteredList = withTransformProps( ({ list, side }) => ({ list: list.filter(char => char.side === side) }))(DisplayList) Render(<FilteredList side="dark" list={SWChars} />)
  43. @coding_lawyer const AddedList = withTransformProps( ({ list, side }) =>

    ({ list: [ …list, { name: 'Han Solo', side: 'light' } ] }))(DisplayList)
  44. @coding_lawyer const withTransformProps = transformFunc => BaseComponent => baseProps =>

    { const transformedProps = transformFunc(baseProps) return <BaseComponent {...transformedProps} /> } const DisplayList = ({ list }) => list.map(char => <div> {/* render UI */} </div>) const FilteredList = withTransformProps( ({ list, side }) => ({ list: list.filter(char => char.side === side) }))(DisplayList) Render(<FilteredList side="dark" list={SWChars} />)
  45. @coding_lawyer const HoC = config => BaseComponent => EnhancedComponent

  46. @coding_lawyer avoid unnecessary duplication

  47. @coding_lawyer DisplayList (presentation) withTransformProps (logic) reusable reusable

  48. @coding_lawyer import { connect } from 'react-redux' const mapStateToProps =

    state => ({ /* piece of global state */ }) const mapDispatchToProps = dispatch => ({ /* global state handlers*/ }) export default connect( mapStateToProps, mapDispatchToProps )(Component)
  49. @coding_lawyer import { connect } from 'react-redux' const mapStateToProps =

    state => ({ /* piece of global state */ }) const mapDispatchToProps = dispatch => ({ /* global state handlers*/ }) export default connect( mapStateToProps, mapDispatchToProps )(Component)
  50. @coding_lawyer import { connect } from 'react-redux' const mapStateToProps =

    state => ({ /* piece of global state */ }) const mapDispatchToProps = dispatch => ({ /* global state handlers*/ }) export default connect( mapStateToProps, mapDispatchToProps )(Component)
  51. @coding_lawyer import { connect } from 'react-redux' const mapStateToProps =

    state => ({ /* piece of global state */ }) const mapDispatchToProps = dispatch => ({ /* global state handlers*/ }) export default connect( mapStateToProps, mapDispatchToProps )(Component)
  52. @coding_lawyer const withCondition = (conditionFn, EitherComponent) => BaseComponent => baseProps

    => conditionFn(baseProps) ? ( <EitherComponent /> ) : ( <BaseComponent {...baseProps} />)
  53. @coding_lawyer React HoCs Reusability Composition Recompose Introduction Conclusion

  54. @coding_lawyer const number = 15 const increment = num =>

    num + 5 const decrement = num => num - 3 const multiply = num => num * 2 const operation = increment(decrement(multiply(number))) console.log(operation) //32
  55. @coding_lawyer const number = 15 const increment = num =>

    num + 5 const decrement = num => num - 3 const multiply = num => num * 2 const operation = increment(decrement(multiply(number))) console.log(operation) //32
  56. @coding_lawyer const number = 15 const increment = num =>

    num + 5 const decrement = num => num - 3 const multiply = num => num * 2 const operation = increment(decrement(multiply(number))) console.log(operation) //32
  57. @coding_lawyer const withTransformProps = transformFunc => BaseComponent => baseProps =>

    { const transformedProps = transformFunc(baseProps) return <BaseComponent {...transformedProps} /> } const withCondition = (conditionFn, EitherComponent) => BaseComponent => baseProps => conditionFn(baseProps) ? ( <EitherComponent /> ) : ( <BaseComponent {...baseProps} /> ) const ConditionedList = withCondition(({ list }) => list.length <= 1, Warning)(DisplayList) const FilteredList = withTransformProps(({ list, side }) => ({ list: list.filter(char => char.side === side) }))(ConditionedList)
  58. @coding_lawyer const DisplayList = ({ list }) => list.map(char =>

    <div> {/* render UI */} </div>) const Warning = () => <div> You need to have more than one character </div> Render(<FilteredList side="dark" list={SWChars} />)
  59. @coding_lawyer HoCs talk to each other via props

  60. @coding_lawyer const ConditionedList = withCondition(({ list }) => list.length <=

    1, Warning)(DisplayList) const FilteredList = withTransformProps(({ list, side }) => ({ list: list.filter(char => char.side === side) }))(ConditionedList)
  61. @coding_lawyer const FilteredList = withTransformProps(({ list, side }) => ({

    list: list.filter(char => char.side === side) }))(withCondition(({ list }) => list.length <= 1, Warning)(DisplayList))
  62. @coding_lawyer const compose = (...hocs) => BaseComponent => hocs.reduceRight((acc, hoc)

    => hoc(acc), BaseComponent) const enhance = compose( withTransformProps(({ list, side }) => ({ list: list.filter(char => char.side === side) })), withCondition(({ list }) => list.length <= 1, Warning) ) const FilteredList = enhance(DisplayList)
  63. @coding_lawyer const withTransformProps = transformFunc => BaseComponent => baseProps =>

    { const transformedProps = transformFunc(baseProps) return <BaseComponent {...transformedProps} /> } const withCondition = (conditionFn, EitherComponent) => BaseComponent => baseProps => conditionFn(baseProps) ? ( <EitherComponent /> ) : ( <BaseComponent {...baseProps} /> ) const enhance = compose( withTransformProps(({ list, side }) => ({ list: list.filter(char => char.side === side) })), withCondition(({ list }) => list.length <= 1, Warning) ) const FilteredList = enhance(DisplayList)
  64. @coding_lawyer withTransformProps (logic) reusable reusable withCondition (logic) DisplayList (presentation) reusable

  65. None
  66. @coding_lawyer React HoCs Reusability Composition Recompose Introduction Conclusion

  67. @coding_lawyer import { withProps, branch, compose } from 'recompose' const

    withWarning = () => () => <div>You need to have more than one character</div> const enhance = compose( withProps(({ list, side }) => ({ list: list.filter(char => char.side === side) })), branch(({ list }) => list.length <= 1, withWarning) ) const FilteredList = enhance(DisplayList)
  68. @coding_lawyer import { withProps, branch, compose, withState } from ‘recompose'

    const withWarning = () => () => ( <div>You need to have more than one character</div> ) const enhance = compose( withState('state', 'setState', 'dark'), withProps(({ list, state }) => ({ filtered: list.filter(char => char.side === state) })), branch(({ filtered }) => filtered.length <= 1, withWarning) )
  69. @coding_lawyer const DisplayList = ({ filtered, setState, state }) =>

    ( <div> {filtered.map(char => ( <div key={char.name}> <div>Character: {char.name}</div> <div>Side: {char.side}</div> </div> ))} <button onClick={() => setState(state === 'dark' ? 'light' : 'dark')}> Toggle </button> </div> ) const FilteredList = enhance(DisplayList) Render(<FilteredList list={SWChars} />)
  70. @coding_lawyer const DisplayList = ({ filtered, setState, state }) =>

    ( <div> {filtered.map(char => ( <div key={char.name}> <div>Character: {char.name}</div> <div>Side: {char.side}</div> </div> ))} <button onClick={() => setState(state === 'dark' ? 'light' : 'dark')}> Toggle </button> </div> ) const FilteredList = enhance(DisplayList) Render(<FilteredList list={SWChars} />)
  71. @coding_lawyer const withWarning = () => () => ( <div>You

    need to have more than one character</div> ) const enhance = compose( withState('state', 'setState', 'dark'), withProps(({ list, state }) => ({ filtered: list.filter(char => char.side === state) })), branch(({ filtered }) => filtered.length <= 1, withWarning), withHandlers({ toggleState: ({ state, setState }) => () => setState(state === 'dark' ? 'light' : 'dark') }) )
  72. @coding_lawyer const withWarning = () => () => ( <div>You

    need to have more than one character</div> ) const enhance = compose( withState('state', 'setState', 'dark'), withProps(({ list, state }) => ({ filtered: list.filter(char => char.side === state) })), branch(({ filtered }) => filtered.length <= 1, withWarning), withHandlers({ toggleState: ({ state, setState }) => () => setState(state === 'dark' ? 'light' : 'dark') }) )
  73. @coding_lawyer const DisplayList = ({ filtered, toggleState }) => (

    <div> {filtered.map(char => ( <div key={char.name}> <div>Character: {char.name}</div> <div>Side: {char.side}</div> </div> ))} <button onClick={toggleState}>Toggle</button> </div> ) const FilteredList = enhance(DisplayList) Render(<FilteredList list={SWChars} />)
  74. @coding_lawyer const withWarning = () => () => ( <div>You

    need to have more than one character</div> ) const enhance = compose( withState('state', 'setState', 'dark'), withProps(({ list, state }) => ({ filtered: list.filter(char => char.side === state) })), branch(({ filtered }) => filtered.length <= 1, withWarning), withHandlers({ toggleState: ({ state, setState }) => () => setState(state === 'dark' ? 'light' : 'dark') }) )
  75. @coding_lawyer Recompose makes HoCs’ composition easier

  76. @coding_lawyer withState (logic) reusable reusable reusable withProps (logic) branch (logic)

    withHandlers (logic) DisplayList (presentation) reusable reusable
  77. @coding_lawyer React HoCs Reusability Composition Recompose Introduction Conclusion

  78. @coding_lawyer class FilteredList extends React.Component { constructor(props) { super(props) this.state

    = { side: 'dark' } } toggleState() { this.setState({ side: this.state.side === 'dark' ? 'light' : 'dark' }) } render() { const filtered = this.props.list.filter( char => char.side === this.state.side) return filtered.length > 1 ? ( <div> {filtered.map(char => ( <div key={char.name}> <div>Character: {char.name}</div> <div>Side: {char.side}</div> </div> ))} <button onClick={() => this.toggleState()}>Toggle</button> </div> ) : (<div>You need to have more than one character.</div>)}}
  79. @coding_lawyer const DisplayList = ({ filtered, toggleState }) => (

    <div> {filtered.map(char => ( <div key={char.name}> <div>Character: {char.name}</div> <div>Side: {char.side}</div> </div> ))} <button onClick={toggleState}>Toggle</button> </div> ) const FilteredList = enhance(DisplayList)
  80. @coding_lawyer const withWarning = () => () => ( <div>You

    need to have more than one character</div> ) const enhance = compose( withState('state', 'setState', 'dark'), withProps(({ list, state }) => ({ filtered: list.filter(char => char.side === state) })), branch(({ filtered }) => filtered.length <= 1, withWarning), withHandlers({ toggleState: ({ state, setState }) => () => setState(state === 'dark' ? 'light' : 'dark') }) )
  81. @coding_lawyer presentational components that can be enhanced by HoC(s)

  82. @coding_lawyer data fetching local / global state local storage /

    cookies
  83. @coding_lawyer const withWarning = () => () => ( <div>You

    need to have more than one character</div> ) const enhance = compose( withState('state', 'setState', 'dark'), withProps(({ list, state }) => ({ filtered: list.filter(char => char.side === state) })), branch(({ filtered }) => filtered.length <= 1, withWarning), withHandlers({ toggleState: ({ state, setState }) => () => setState(state === 'dark' ? 'light' : 'dark') }) )
  84. @coding_lawyer const withWarning = () => () => ( <div>You

    need to have more than one character</div> ) const enhance = compose( withState('state', 'setState', 'dark'), withProps(({ list, state }) => ({ filtered: list.filter(char => char.side === state) })), branch(({ filtered }) => filtered.length <= 1, withWarning), withHandlers({ toggleState: ({ state, setState }) => () => setState(state === 'dark' ? 'light' : 'dark') }) )
  85. @coding_lawyer 
 @coding_lawyer codinglawyer.net

  86. @coding_lawyer Questions?

  87. @coding_lawyer Thank You

  88. @coding_lawyer @coding_lawyer codinglawyer.net github.com/codinglawyer/hocs-code