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