Learn You Recompose

With a Side of Functional Programming

Who Am I?

Kyle Shevlin

  • Senior Software Engineer at Fastly
    • Lover of JavaScript
    • Hater of Semicolons (in my JavaScript)
  • Twitter - @kyleshevlin

What will we cover?

Recompose

Basic FP

  • Functions as First-Class Citizens
  • Higher Order Functions
  • Currying & Partial Application
  • Composition

How to write our own HOCs

How to use Recompose

(of course!)

And some demos

Basic Functional Programming

...or The Minimum Amount of Functional Programming You Need to Know to be a Danger to Yourself and Coworkers :)

Functions as First Class Citizens


    function add (x, y) {
        return x + y
    }

    const sum = add(add(4, 3), 2)

    console.log(sum) // 9

"Functions as first class citizens" means you can pass function anywhere as if it's a variable.

Higher Order Functions

A "higher order function" does one of the following:

  • Takes a function as an argument
  • Returns a function

    function emojiLogs (emoji) {
        return function (...strs) {
            console.log(`${emoji} ${[...strs].join(' ')}`)
        }
    }

    const catLog = emojiLog(😻)
    const dogLog = emojiLog(🐶)
    const logLog = emojiLog(🌲)

    catLog('Krios', 'Tali')
    // 😻 Krios Tali

    dogLog('<- Why do I not have one of these yet?!')
    // 🐶 <- Why do I not have one of these yet?!

    logLog('Douglas Firs are real nice!')
    // 🌲 Douglas Firs are real nice!

Currying & Partial Application

Currying is the technique of refactoring a function that normally accepts multiple arguments into one that accepts them one at a time.

 

A curried function will continue to return a unary function for each argument until the final one is provided. Then, the function is evaluated.

 

The arguments provided to a curried function are said to be "partially applied" while the function awaits its final argument.

The Canonical Example


    // add : Number -> Number -> Number
    const add = x => y => x + y

    const add2 = add(2) // The number 2 is partially applied

    const add5 = add(5) // The number 5 is partially applied

    const eight = add5(3) // 8

    console.log(add2(eight) // 10

Composition

If you remember high school math, composition is the act of passing the result of one function to another function.

 

This takes the form of `f(g(x))` where `f` and `g` are both functions. But nesting functions in JavaScript can be really ugly.


    const scream = str => str.toUpperCase()

    const exclaim = str => `${str}!`

    const repeat = str => `${str} ${str}`

    const warnSteve = repeat(exclaim(scream('Steve, the world is coming to an end')))

    console.log(warnSteve)
    // STEVE, THE WORLD IS COMING TO AN END! STEVE, THE WORLD IS COMING TO AN END!

Instead of nesting functions, what if we used currying and partial application to create a function that would do the composition for us?

    
    // Pretend we imported `scream`, `exclaim`, and `repeat` from the previous slide

    const compose = (...fns) => value =>
        fns.reduceRight((accumulated, fn) => fn(accumulated), value)

    const withGusto = compose(
        repeat,
        exclaim,
        scream
    )

    const warnSteve = withGusto('Steve, the world is coming to an end')

    console.log(warnSteve)
    // STEVE, THE WORLD IS COMING TO AN END! STEVE, THE WORLD IS COMING TO AN END!

Higher Order Components

What is a HOC?

A higher order component is a function that takes a Component as one of its arguments and returns a new Component.


    // identityHoc : Component A -> Object -> Component B
    const identityHoc = BaseComponent => props => <BaseComponent {...props} />

    // User : Object -> Component
    const User = ({ name, age }) =>
        <div className='user'>
            <h3 className='user-name'>{name}</h3>
            <div>Age: {age}</div>
        </div>

    const AlsoUser = identityHoc(User) 

    React.render((
        <div>
            // Both these will render identically
            <User name='Kyle' age='32' />
            <AlsoUser name='Kyle' age='32' />
        </div>
    ), document.getElementById('app'))

A Basic HOC

Why Use HOCs?

  • DRYs up code
  • Separates logic and view concerns
  • Are composable

 

Recompose API

Finally!

You're working with stateless functional components, what might be the first thing you need to make them more useful?

withState

The withState higher order component allows you to add state to SFCs which get passed as props to the component.


    // withState(
    //   stateName: string,
    //   stateUpdaterName: string,
    //   initialState: any | (props: Object) => any
    // ): HigherOrderComponent

    const withToggle = withState('isToggled', 'setToggle', false)

withHandlers

The withHandlers higher order component allows you to add handler functions for updating state in SFCs. Also passed as  to SFCs which get passed as props to the component.


    // withHandlers(
    //   handlerCreators: {
    //     [handlerName: string]: (props: Object) => Function
    //   }
    // ): HigherOrderComponent

    const withToggleHandlers = withHandlers({
      show: ({ toggle }) => () => toggle(true),
      hide: ({ toggle }) => () => toggle(false),
      toggle: ({ toggle }) => () => toggle(x => !x)
    })

withReducer

The withReducer 

higher order component allows you to use a Redux-like reducer to handle state.


    // withReducer<S, A>(
    //   stateName: string,
    //   dispatchName: string,
    //   reducer: (state: S, action: A) => S,
    //   initialState: S | (ownerProps: Object) => S
    // ): HigherOrderComponent

    const withToggleReducer = withReducer(
      'isToggled',
      'dispatch',
      (state, action) => {
        switch (action.type) {
          case 'SHOW':
            return true

          case 'HIDE':
            return false

          case 'TOGGLE'
            return !state

          default:
            return state
        }
      },
      false
    )

lifecycle

The lifecycle higher order component allows you to add all the lifecycle methods (except for render) to a SFC.


    // lifecycle(
    //   spec: Object,
    // ): HigherOrderComponent

    const withData = lifecycle({
      componentDidMount () {
        fetch('data.json')
          .then(response => response.json())
          .then(data => this.setState({ data })
      }
    })

pure

The pure higher order component returns a `PureComponent` version of your SFC.

💥

Next slide.

mapProps

The mapProps higher order component allows you to massage the ownerProps for the base component.


    // mapProps(
    //  propsMapper: (ownerProps: Object) => Object,
    // ): HigherOrderComponent

    const alwaysSteve = mapProps({ name: 'steve' })

withProps

The withProps higher order component allows you to merge new props with the ownerProps of the base component.


    // withProps(
    //  createProps: (ownerProps: Object) => Object | Object
    // ): HigherOrderComponent

    const mergeSteveAndKeepTheRest = withProps({ name: 'Steve' })

renderComponent

The renderComponent higher order component allows you to... well, render a component. It's useful in conjunction with the next HOC.


    // renderComponent(
    //   Component: ReactClass | ReactFunctionalComponent | string
    // ): HigherOrderComponent

    const User = ({ name }) => <span>{name}</span>

    const hocUser = renderComponent(User)

branch

The branch higher order component allows you to conditionally choose between higher order components. Really powerful when used with renderComponent or renderNothing


    // branch(
    //   test: (props: Object) => boolean,
    //   left: HigherOrderComponent,
    //   right: ?HigherOrderComponent
    // ): HigherOrderComponent

    const Left = () => <span>Left</span>
    const Right = () => <span>Right</span>

    const predicate = ({ isLoading }) => isLoading

    const withLoading = branch(
        predicate,
        renderComponent(Left),
        renderComponent(Right) // This can be implicitly supplied by the base component
    )

We'll do it live!

withSpinnerWhileLoading

We'll make a higher order component that will show a spinner until our data has loaded that we can use on any similar component.

Resources

Learn You Recompose

By Kyle Shevlin

Learn You Recompose

Learn to use and abuse higher order components with the Recompose library.

  • 1,181