Recompose Yourself

Mitch Masia

Higher-Order Components in React

Hexient Labs

Mitch Masia

Functions

HOCs

Recompose

In practice

Functions

(x) => x * 2

(name) => {...email}

() => () => {}

Functions

// Function that returns a comparator function
const isMultipleOfX = x => 
  candidate => candidate % x === 0

// Checks if number is a multiple of 3
const isMultipleOf3 = isMultipleOfX(3)
isMultipleOf3(12) // true

// Checks if a number is a multiple of 5
const isMultipleOf5 = isMultipleOfX(5)
isMultipleOf5(15) // true

Pure Functions

const sumPure = (x, y) => x + y

No side-effects

Same inputs -> same output

Benefits of Purity

Reproducable

Testable

Debuggable

No Side-Effects

Cacheable

React as a Paradigm

{} => </>

Let's Forget about the UI

Pure functional components

const Header = props => <h1>{props.content}</h1>

Take in Props, return jsx reproducably

Pure functional components

Extreme traceability

Encourage Granulatity

Testable (snapshottable)

No functionality  😟

Decoupled from data

Higher-Order Components

"enhancing" our dumb components

const enhancer = BaseComponent =>
  class Wrapper extends Component {
    render() {
      return <BaseComponent {...this.props} />
    }
  }

Higher-Order Components (HOCs)

DRY

Repeatable Functionality

Higher-Order Components

const withTitle = BaseComponent =>
  class Wrapper extends Component {
    render() {
      return (
        <div>
          <h1>Here is a title</h1>
          <BaseComponent {...this.props} />
        </div>
    }
  }

Higher-Order Components Factory

const withTitle = title => BaseComponent =>
  class Wrapper extends Component {
    render() {
      return (
        <div>
          <h1>{title}</h1>
          <BaseComponent {...this.props} />
        </div>
    }
  }

Higher-Order Components

const withMountingAction = BaseComponent =>
  class Wrapper extends Component {
    componentDidMount() {...}

    render() {
      return <BaseComponent {...this.props} />
    }
  }

Aha moment!

import withMountingAction from './withMountingAction'

const Header = props => (<h1>{props.content}</h1>)

export default withMountingAction(Header)

Our (testable) pure functional component is "enhanced" with a lifecycle hook

We can write Class-based components for everything, But it gets super tedious and repetitive.

We can write hocs for everything, But it gets super tedious and repetitive.

enter recompose

Recompose is a React utility belt for function components and higher-order components. Think of it like lodash for React.

Enter recompose

Recompose exports pure functions that return hocs we use as enhancers

<recompose_function>(config) => BaseComp => EnhancedComp

Most Recompose functions are not hocs themselves - They are hoc factories

🤨

contrived example

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class Header extends Component {
  static propTypes = {
    content: PropTypes.string.isRequired,
  }

  render() {
    return <h1>{this.props.content}</h1>
  }
}

contrived example

import React, { Component } from 'react'
import { setPropTypes } from 'recompose'
import PropTypes from 'prop-types'

class Header extends Component {
  render() {
    return <h1>{this.props.content}</h1>
  }
}

// Create enhancer
const withPropTypes = setPropTypes({
  content: PropTypes.string.isRequired,
})

export default withPropTypes(Header)

contrived example

import React from 'react'
import { setPropTypes } from 'recompose'
import PropTypes from 'prop-types'

const Header = props => (<h1>{props.content}</h1>)

const withPropTypes = setPropTypes({
  content: PropTypes.string.isRequired,
})

export default withPropTypes(Header)

contrived example

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class Header extends Component {
  static propTypes = {
    content: PropTypes.string.isRequired,
  }

  static defaultProps = {
    content: 'Hello, world!',
  }

  render() {
    return <h1>{this.props.content}</h1>
  }
}

contrived example

import React from 'react'
import { defaultProps, setPropTypes } from 'recompose'
import PropTypes from 'prop-types'

const Header = props => (<h1>{props.content}</h1>)

const withPropTypes = setPropTypes({
  content: PropTypes.string.isRequired,
})

const withDefaultProps = defaultProps({
  content: 'Hello, world!',
})

export default withPropTypes(withDefaultProps(Header))

contrived example

import React from 'react'
import { compose, defaultProps, setPropTypes } from 'recompose'
import PropTypes from 'prop-types'

const Header = props => (<h1>{props.content}</h1>)

const withPropTypes = setPropTypes({
  content: PropTypes.string.isRequired,
})

const withDefaultProps = defaultProps({
  content: 'Hello, world!',
})

export default compose(
  withDefaultProps,
  withPropTypes
)(Header)

Lame Example; Important Topics

- Our Component never changed

- EnhanceR architecture  -->  decoupling

- HOC Composition

compose(withDefaultProps, withPropTypes)(Header)

// is the same as this; HOCs are applied LTR
withPropTypes(withDefaultProps(Header))

Less contrived

import React from 'react'
import { FlatList } from 'react-native'

const CustomList = props => <FlatList {...props} />

export default CustomList

CustomList is a proxy component, we will make it better

Less contrived

import React from 'react'
import { compose, setPropTypes } from 'recompose'
import { FlatList } from 'react-native'

const CustomList = props => <FlatList {...props} />

// Enhancers
const withPropTypes = setPropTypes({
  data: PropTypes.array.isRequired,
})

export default compose(withPropTypes)(CustomList)

Less contrived

import React from 'react'
import { compose, defaultProps, setPropTypes } from 'recompose'
import { FlatList } from 'react-native'

const CustomList = props => <FlatList {...props} />

// Enhancers
const withPropTypes = setPropTypes({
  data: PropTypes.array.isRequired,
})

const withDefaultProps = defaultProps({
  data: [],
})

export default compose(withDefaultProps, withPropTypes)(CustomList)

Less contrived

import React from 'react'
import { branch, compose, defaultProps, renderNothing, setPropTypes } from 'recompose'
import { FlatList } from 'react-native'

const CustomList = props => <FlatList {...props} />

// Enhancers
const withPropTypes = setPropTypes({
  data: PropTypes.array.isRequired,
})

const withDefaultProps = defaultProps({
  data: [],
})

const withEmptyState = branch(
  ownProps => !ownProps.data.length,
  renderNothing(),
)

export default compose(
  withDefaultProps,
  withEmptyState,
  withPropTypes,
)(CustomList)

In english

Step 1: The prop 'data' should default to []

compose(
  withDefaultProps,
  withEmptyState,
  withPropTypes,
)(CustomList)

Step 2: If !data.length, show nothing

Step 3: The data prop should be an array

In english

Our CustomList never changed, but we have automatically taken care of rendering a non-ideal state

compose(
  withDefaultProps,
  withEmptyState,
  withPropTypes,
)(CustomList)

optimizations

import React from 'react'
import { compose, pure } from 'recompose'
import { FlatList } from 'react-native'

const CustomList = props => <FlatList {...props} />

export default compose(pure)(CustomList)

Pure is one of the rare occurrences of recompose directly exporting a HOC

optimizations

import React from 'react'
import { compose, onlyUpdateForKeys } from 'recompose'
import { FlatList } from 'react-native'

const CustomList = props => <FlatList {...props} />

// Enhancers
const withPropComparator = onlyUpdateForKeys(['data'])

export default compose(withPropComparator)(CustomList)

Forget about "Shouldcomponentupdate"

optimizations

import React from 'react'
import { compose, shouldUpdate } from 'recompose'
import { FlatList } from 'react-native'

const CustomList = props => <FlatList {...props} />

// Enhancers
const withPropComparator = shouldUpdate(
  (currentProps, nextProps) => {...}
)

export default compose(withPropComparator)(CustomList)

withmountingaction

import React, { Component } from 'react'

const withMountingAction = BaseComponent =>
  class Wrapper extends Component {
    componentDidMount() {...}

    render() {
      return <BaseComponent {...this.props} />
    }
  }

withmountingaction, revisited

import { lifecycle } from 'recompose'

const withMountingAction = lifecycle({
  componentDidMount() {...}
})

export default withMountingAction

What have we learned

React is a paradigm relying on pure functions

Pure functional components are glorious because of their extreme visibility

HOCs are extremely useful to enhance functional components (decoupling)

We don't have to modify our dumb components at all :)

What have we learned

Recompose is the wheel, let's not reinvent it

Thanks + ?

Thanks + ?

Because that GIF is distracting.

Made with Slides.com