Why The Hipsters Recompose Everything

Building a Utility Library for React

Introduction

This is a short run through of a couple of efficient utility functions that you might find helpful when working with React. We will explore a couple of typical situations, where using a utility function makes sense by either removing boilerplate or making the code more readable.

It’s important to note that these functions are catered towards function components but should work for class components none the less. You will probably not need a couple of the functions presented here, if you prefer the latter approach. That being said, you might gain some insight into Ramda along the way, which is always valuable. It’s also important to note, that we can interchange most Ramda functions with lodash/fp functions if you prefer lodash.

I would advise to go through the different sections even if it sounds like a stressful ideas to create a utils library. At least make sure to read the the final section. If you are already using a utils library or created your own, you might still find the implementations interesting or helpful.

Higher Order Components

High order components are functions that expect a component and return a new component.

HOC :: Component -> Component

Let’s use Ramda’s compose and curry to create a hoc function. All that hoc does is expect two components and returns the resulting component composition.

const hoc = curry((Comp1, Comp2) => compose(Comp1, Comp2))

So now that we have a function hoc, we can start building specialized functions based on the same principle.

Mapping Props

What if we only want a subset of the props or we want to calculate a new prop, depending on other props? mapProps to the rescue.

const mapProps = mapFn => Component => {

const factory = createFactory(Component)

return props => factory(mapFn(props))

}

mapProps expects a mapping function which returns a new function that itself awaits a component. We can use createFactory as a short-hand function for generating createElement for a given type. After creating the factory we return a new function which applies the mapping function with the passed in props. Let’s refactor mapProps.

const mapProps = curry((mapFn, Component) => {

const factory = createFactory(Component)

return compose(factory, mapFn)

})

Now this enables us to define a high order Component that filters any unwanted props via omit f.e.

const enhance = mapProps(props => omit(['foo'], props))

We could also rewrite the previous enhance because most Ramda functions are curried.

const enhance = compose(mapProps, omit(['foo']))

Or use pick to only filter the props we want, again because pick is curried, we can easily compose pick and mapProps. No need for any mentioning of props inside enhance in this case.

const enhance = compose(mapProps, pick(['bar']))

Once we have a mapProps function defined, we can pass in any component and transform the passed in props as we like.

const enhance = compose(mapProps, pick('bar'))

const EnhancedComponent = enhance(({ bar }) => <h1>{bar}</h1>)

withProps

We can even build another function that helps to define basic props by using mapProps. Let’s call this function withProps.

const withProps = input => mapProps(props => ({...props, ...input}))

Taking this a step further, what about if withProps could either be a function or an object? No problem we can adapt withProps to handle both cases. Ramda’s is comes handy here. Let’s define a short hand function for checking if something is a function.

const isFunction = is(Function)

Using the isFunction we can check if the input is a function or an object.

const withProps = input => mapProps(props => ({

...props,

...(isFunction(input)? input(props) : input)

}))

This enables us to define additional props that are merged with the passed in props.

Conditionals inside render

Using conditionals inside the render function can quickly lead to messy code if we’re not careful. What if we could use something like an ifElse or branch function to define what gets rendered, something like the following.

const SomeCoolComponent = ({ users }) => (

branch(isEmpty(users), <IsLoading />, <Users users={users} />)

)

Instead of messing up the render function with a number of conditionals, just roll out a branch function, that expects a predicate function and two components.

const branch = curry((predicateFn, Comp1, Comp2) =>

predicateFn ? Comp1 : Comp2)

Depending on the conditional we either render the first or the second component.

const SomeCoolComponent = ({ users, items }) => (

// ...

branch(isEmpty(users), <IsLoading />, <Users users={users} />)

// ...

)

Now you might have a situation like this for example.

const SomeCoolComponent = ({ users, items }) => (

// ...

{ items.length > 1 && <Item items={items} /> }

// ...

)

No problem, lets define a RenderNothing function.

const RenderNothing = () => null

Using RenderNothing is trivial.

const SomeCoolComponent = ({ users, items }) => (

// ...

branch(isEmpty(items), <RenderNothing />, <Item items={items} />)

// ...

)

We could also exchange RenderNothing with null.

branch(isNotEmpty(items), <Item items={items} />, null)

Handling Component State

What if we want a functions everywhere approach? How do we handle local component state in cases where we need to?

Again, let’s cover a common case and define a function that expects a component, a state name and a state update function and returns a new component that has a props with the defined names and handlers.

WithState

We’re using createClass in the following implementation, but ES6 classes or other approaches will work just as fine. This is a matter of choice.

const withState = (stateName, stateUpdateFn, initialState) => {

return Comp => {

const factory = createFactory(Comp)

return createClass({

getInitialState() { return { value: initialState } },

stateUpdateFn(fn) {

this.setState(({ value } ) => ({ value: fn(value) }))

},

render() {

return factory({

...this.props,

[stateName] : this.state.value,

[stateUpdateFn] : this.stateUpdateFn,

})

}

})

}

}

This enables to us to use withState whenever we need to manage local component state.

const enhance = withState('counter', 'setCounter', 0)

const Counter = enhance(({ counter, setCounter }) =>

<div>

Count: {counter}

<button onClick={() => setCounter(n => n+1)}>Increment</button>

<button onClick={() => setCounter(n => n-1)}>Decrement</button>

</div>

)

WithReducer

The next utility function is similar to withState except that we can define a reducer and a dispatcher to handle the state. Think of it as redux encapsulated inside a single component.

const withReducer =

(stateName, dispatchName, reducer, initialState) => {

return Comp => {

const factory = createFactory(Comp)

return createClass({

getInitialState() { return {value: initialState} },

dispatch(action) {

this.setState(({ value }) =>

({ value: reducer(value, action) })

)

},

render() {

return factory({

...this.props,

[stateName] : this.state.value,

[dispatchName] : this.dispatch,

})

}

})

}

}

After defining a reducer function that expects state and action, withReducer will pass in the defined state as well as a dispatch method to trigger actions that update the local component state.

const reducer = (state, action) => {

console.log(state, action)

switch(action.type) {

case 'INC': return state + 1

case 'DEC': return state - 1

default: return state

}

} const enhance = withReducer('counter', 'dispatch', reducer, 10) const Counter = enhance(({ counter, dispatch }) => (

<div>

<span>counter: { counter }</span>

<button onClick={ () => dispatch({ type: 'INC'})}>Inc</button>

<button onClick={ () => dispatch({ type: 'DEC'})}>Dec</button>

</div>

))

From Function To Class and Back

Converting from a function to a class component and vice versa is just a couple of lines.

const fnToClass = (Fn, spec = {}) => createClass({

...spec,

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

}) const classToFn = Cls => props => <Cls {…props} />

All credits to Brian Lonsdorf for this one. It fits into a tweet, check for yourself https://twitter.com/drboolean/status/767443725597282304.

We can do one refinement in fnToClass by adding a second optional argument, spec, like so:

const fnToClass = (fn, spec = {}) => createClass({

...spec,

render() { return fn(this.props) },

})

Now we can hook into the lifecycle if needed.

Hooking into the Lifecycle

Imagine we have a component function called counter and we want to define some default props in a given situation. Our fnToClass, as seen before, has a second optional argument spec.

By using spec, we can define default state or props for example.

const EnhancedFnComponent = fnToClass(fnComponent, {

getDefaultProps() { return { items: [] } },

})

Pure

What if we need to update when something has changed instead of rendering the function component every time props have been passed down?

No big issue, as seen above, we can hook into the lifecycle via fnToClass and define a shouldComponentUpdate function.

const pure = curry((shouldComponentUpdate, component) =>

fnToClass(component, { shouldComponentUpdate })

)

pure itself is now a function expecting a predicate function and a component. Due to the fact that we’re currying pure, we can define different pure functions depending on wether we’re using immutable-js or not f.e.

const toPureImmutableComponent = pure(

function (nextProps) {

return this.props !== nextProps

}

)

By using toPureImmutableComponent we’re able to convert a function component into a pure component.

const EnhancedComponent =

toPureImmutableComponent(BaseComponent)

Concat

Combine two components? No problem.

const EnhancedComp1 = mapProps(pick(['foo']), MyComp1)

const EnhancedComp2 = mapProps(pick(['bar']), MyComp2) const concat = curry((comp1, comp2) => props => (

<div>{ comp1(props) } { comp2(props) }</div>

)) const ConcatedComponents = concat(EnhancedComp1, EnhancedComp2)

Compose Everything

We can even compose most of our previous functions, like for example combining mapProps, withReducer, and toPureImmutableComponent to output a new component depending on the defined state and props. Ramda’s compose is all we need to achieve this.

const enhance = compose(

mapProps(omit(['foo'])),

withReducer('counter', 'dispatch', counterReducer, 10),

toPureImmutableComponent

) const EnhancedComponent = enhance(BasicComponent)

It might be interesting to note, that the functions compose from left to right in this specific situation.

Bonus: No switch statements in reducers

I have written about this in the The Elegance Of React post. In case you haven’t read it or forgotten how to implement your own switch-free reducer, here’s a possible implementation to get rid of the switch statements.

// alternative is to use defaultTo instead of propOr

const createReducer = (init, handlers) =>

(state = init, action) =>

propOr(identity, prop('type', action), handlers)(state, action)

This enables us to write our reducers like this. No switch statements.

const todos = createReducer([], {

[ADD_TODO]: (state, action) => [

{ id: getNextId(state), completed: false, text: action.text },

...state

],

[DELETE_TODO]:(state, action) =>

reject(propEq('id', action.id), state),

})

Use Recompose

The good news is that we don’t need to build our own utility library from scratch as most functions presented here already come with a utility library called Recompose. We should have gained some insights into how to expand and adapt recompose when needed. This is always valuable.

So simply use Recompose. A library written by Andrew Clark which is like Ramda or Lodash for React. To quote the documentation:

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

https://github.com/acdlite/recompose/#recompose

It offers a large number of useful helper functions for creating higher order components, suited towards the need for optimizations like shouldComponentUpdate or handling local state.

The interesting part is that recompose also plays nice with Ramda or lodash/fp. Most functions presented here, are implemented in a more optimized way in Recompose, which means you might start using then today. This is especially useful if you mainly prefer function components.

Another great example to look into is rebass-recomposed by Brent Jackson, as the name already implies, it’s a great showcase for how to use recompose with an existing library, rebass in this specific case.

Outro

If there is interest, I might consider building a presentation slides library using React, Recompose and Rebass as an example/showcase.

Connect via Twitter