Recompose

@danieljohngrant

Front-end dev at Lost My Name

var Presentation = ({ title, desc }) => (
  <div>
    <h1>{title}</h1>
    <p>{desc}</p>
  </div>
);

@danieljohngrant

var Presentation = ({ title, desc }) => (
  <div>
    <h1>{title}</h1>
    <p>{desc}</p>
  </div>
);

var Container = class extends component {
  componentDidMount() {
    fetch(url)
      .then(response => response.json())
      .then(data => {
        this.setState({
           title: data.title,
           desc: data.description
        })
      });
  }
  render() {
    <Presentational title={this.state.title} desc={this.state.desc} />
  }
}

@danieljohngrant

var Presentation = ({ title, desc }) => (
  <div>
    <h1>{title}</h1>
    <p>{desc}</p>
  </div>
);

var Container = withProps({
  title: 'Lorem ipusm',
  desc: 'Ipsum lorem'
});

@danieljohngrant

withProps({
  title: 'Lorem ipusm',
  desc: 'Ipsum lorem'
});

mapProps(props => ({
  ...props,
  title: props.heading
});

branch(
  props => !!props.title,
  Presentation,
  renderComponent(Loading)
);

withState('counter', 'setCounter', 0);

@danieljohngrant

Lodash for React

import compose from 'recompose/compose';

var componentEnhancer = compose(
  withState('counter', 'setCounter', 0),
  withProps(({ setCounter }) => ({
    increment: () => setCounter(n => n + 1),
    decrement: () => setCounter(n => n - 1),
    reset: () => setCounter(0)
  }))
);

@danieljohngrant

import compose from 'lodash/compose';

var componentEnhancer = compose(
  withState('counter', 'setCounter', 0),
  withProps(({ setCounter }) => ({
    increment: () => setCounter(n => n + 1),
    decrement: () => setCounter(n => n - 1),
    reset: () => setCounter(0)
  }))
);

@danieljohngrant

import compose from 'ramda/lib/compose';

var componentEnhancer = compose(
  withState('counter', 'setCounter', 0),
  withProps(({ setCounter }) => ({
    increment: () => setCounter(n => n + 1),
    decrement: () => setCounter(n => n - 1),
    reset: () => setCounter(0)
  }))
);

@danieljohngrant

Applying functional composition in React apps

@danieljohngrant

(a short story)

import BookPreview from './BookPreview';

var enhance = compose(
  withState('activeImages', 'setActiveImages', [0, 1]),

  withHandlers({
    onIncrement: props => () => 
      props.setActiveImages(props.activeImages.map(id => id + 2)),

    onDecrement: props => () => 
      props.setActiveImages(props.activeImages.map(id => id - 2))
  })
);

export default enhance(BookPreview);

@danieljohngrant

import BookPreview from './BookPreview';
import reducer, { incrementSpread, decrementSpread } from './duck';

var enhance = compose(
  withReducer('activeSpread', 'dispatch', reducer, [0, 1]),

  withHandlers({
    onIncrement: props => () => props.dispatch(incrementSpread()),
    onDecrement: props => () => props.dispatch(decrementSpread())
  })
);

export default enhance(BookPreview);

@danieljohngrant

import { connect } from 'react-redux';
import BookPreview from './BookPreview';
import { getActiveSpread, incrementSpread, decrementSpread } from './duck';

var enhance = compose(
  connect(
    state => ({
      activeSpread: getActiveSpread(state)
    })
  ),
  withHandlers({
    onIncrement: props => () => props.dispatch(incrementSpread()),
    onDecrement: props => () => props.dispatch(decrementSpread())
  })
);

export default enhance(BookPreview);

@danieljohngrant

var enhance = compose(
  mapAsyncProps(fetchDataFromPlatform),
  when(
    props => props.loading,
    renderComponent(BookPreview),
    C => C
  )
);

@danieljohngrant

Currying,

Higher order functions, Composition

var add = (x, y) => x + y

// add(1, 2) -> 3

var add = x => y => x + y

// add(1)(2) -> 3

@danieljohngrant

var add = x => y => x + y

var addOne = add(1)

// addOne = y => 1 + y;
// addOne = y => { var x = 1; return add(x, 1); }

addOne(1) // 2

@danieljohngrant

No.

// add(1), times(2)

@danieljohngrant

Pipe

Compose functions

left to right

// add(1), times(2)

pipe(
  add(1), 
  times(2)
)(1) 

// -> 4

@danieljohngrant

// add(1), times(2)

var doMaths = pipe(
  add(1), 
  times(2)
)

doMaths(1) // -> 4

@danieljohngrant

// v -> node
var createElement = value => ({ type: 'div', value })

createElement('hello') 
// { type: 'div', value: 'hello' }

@danieljohngrant

createElement('hello') 
// -> { type: 'div', value: 'hello' }

createEnhancedElement('hello') 
// -> { type: 'div', value: 'hello mum' }

@danieljohngrant

// v -> node 
var createEnhancedElement = v => 
  createElement(v + ' mum')

@danieljohngrant

Enhance

take a function 

return an enhanced function

// fn -> fn

var enhance = fn => 
  v => fn(v + ' mum')

var createEnhancedElement = enhance(createElement)

@danieljohngrant

// fn -> fn
// (v -> node) -> (v -> node)
var enhance = fn => 
  v => fn(v + ' mum')

var createEnhancedElement = enhance(createElement)

@danieljohngrant

// fn -> fn
var enhance = fn => 
  v => fn(v + ' mum')

// (transform, fn) -> fn
var mapValue = transform => fn => 
  v => fn(transform(v))

@danieljohngrant

// fn -> fn
var enhance = fn => 
  v => fn(v + ' mum')

// (transform, fn) -> fn
var mapValue = transform => fn => 
  v => fn(transform(v)

// v -> v
var transform = v => v + ' mum'

var createEnhancedElement = mapValue(transform)(createElement)

@danieljohngrant

// fn -> fn
var enhance = pipe(
  mapValue(v => v + ' mum'),
  mapValue(v => v.toUpperCase)
)

var createEnhancedElement = enhance(createElement)

@danieljohngrant

// fn -> fn
var enhance = pipe(
  mapValue(v => v + ' mum'),
  mapValue(v => v.toUpperCase)
)

var createEnhancedElement = enhance(createElement)

// {type: "div", value: "HELLO mum"}

@danieljohngrant

Compose

Compose functions

right to left

// fn -> fn
var enhance = compose(
  mapValue(v => v + ' mum'),
  mapValue(v => v.toUpperCase)
)

var createEnhancedElement = enhance(createElement)

// {type: "div", value: "HELLO MUM"}

@danieljohngrant

Enter React

Higher order components

// C -> C
var withEasterEgg => BaseComponent => 
  React.createClass({
    render() {
      return <BaseComponent {...this.props} easterEggEnabled />      
    }
  })

@danieljohngrant

// (props, C) -> C
var withProps => myProps => BaseComponent => 
  React.createClass({
    render() {
      var props = { ...this.props, myProps }
      return <BaseComponent {...props} />      
    }
  })

@danieljohngrant

import MyComponent from './MyComponent'

var enhance = compose(
  witEasterEgg,
  withProps({
    visible: false,
    with: 500
  })


export default enhance(MyComponent);

@danieljohngrant

Resources

https://github.com/acdlite/recompose

YouTube - Andrew Clark - Recomposing your React application at react-europe 


Challenges

 

  • Counter using withState
  • Counter with limits using withReducer
  • Prefix a label using mapProps
  • Add an Easter Egg when counter reaches limit using branch
  • Implement your own HoC
    • log props
    • map async props

This slide was intentionally left blank

// PIPE RECAP
// Calls functions left to right

pipe(add(1), times(2), divide(3))

// (fn3: v => v)((fn2: v => v)((fn1: v => v)))
divide(3)(times(2)(add(1)(v)))

@danieljohngrant

// PIPE RECAP
// Calls functions left to right

pipe(add(1), times(2), divide(3))

// (divide: v => v)((times: v => v)((add: v => v)))
divide(3)(times(2)(add(1)(v)))


// Higher order functions!
pipe(mapValue(concatMum), mapValue(toUpper))

// (fn2: fn => fn)((fn1: fn => fn))
mapValue(toUpper)(mapValue(concatMum)(fn))

// (fn2: (v => v) => (v => v))((fn1: (v => v) => (v => v)))
v => fn(toUpper(v))(
  v => fn(concatMum(v))
)

@danieljohngrant

// PIPE RECAP
// Calls functions left to right

pipe(add(1), times(2), divide(3))

// (divide: v => v)((times: v => v)((add: v => v)))
divide(3)(times(2)(add(1)(v)))


// Higher order functions!
pipe(mapValue(concatMum), mapValue(toUpper))

// (fn2: fn => fn)((fn1: fn => fn))
mapValue(toUpper)(mapValue(concatMum)(fn))

// (fn2: (v => v) => (v => v))((fn1: (v => v) => (v => v)))
v => fn(toUpper(v))(
  v => fn(concatMum(v))
)

var firstFn = v => fn(concatMum(v))
var secondFn = v => first(v => fn(toUpper(v)))

firstFn(secondFn(v))

@danieljohngrant

Copy of Recompose

By Alessandro Annini

Copy of Recompose

  • 454