Recomposing Redux

October 25, 20177 min read

https://unsplash.com/photos/osSryggkso4

A few weeks ago, I spent some time playing around with the recompose library.

Recompose is a set of helpers that create Higher Order Components (HOC) for React and add encapsulate behaviours that you can apply to any React component.

While making stuff on codesandbox (This tool is awesome you should use it!), I decided that I would build a tiny redux clone with what I had just learnt.

What is a Higher-order component ?

In functional programming, we call a « Higher Order Function » a function that takes functions as parameters and/or returns a new function.

// Here is our higher order function
const andThen = (fn) => (promise) => promise.then(fn)

// Here is a simple function (or first-order function)
const logger = (x) => console.log(x)

// andThen(logger) returns a new function that expects a promise !
const logPromise = andThen(logger)

Now let’s use our logPromisefunction :

const p1 = Promise.resolve(42)

logPromise(p1)
// logs '42'

Now you now what is a higher order function. I’m sure that you’ve already used it before. These functions are everywhere in Javascript. (Hint hint Array.forEach 😉 😉)

By using the same analogy, a « Higher Order Component » (or HOC) is a function that takes React components as parameters and/or returns a React component.

Now that we know what is a HOC, let’s use Recompose on a simple example.

Let’s try Recompose

Recompose gives us helper functions that let us create HOCs.

These helpers can be composed into a and add behaviours to any react components. They accept a base component and return a new component with additional functionality.

Here is a little exercise :

What if you could build react components without relying on classes/this ?

Here’s an example from the docs :

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>
)

Here the withStateHOC takes 3 params :

  1. the key 'counter'is the name of the field where you will store your state, much like you’d do on class components with this.state.counter
  2. the key 'setCounter'is the name of the function that will update the state counterwith a new value, like this.setState(oldState => ({counter: oldState + 1 }))
  3. the initial value of 'counter'

The value of the state counterand the state updater setCounterwill be passed down as props to your component wrapper by the HOC enhance

It’s like using setState but in a functional react component! This is huge!

And there are more helpers available likemapProps, defaultProps, withContext, withReducer, withHandler, lifecycle(to hook to React lifecycle callbacks) … (and many more!)

If you want to read more about Recompose, I suggest you this excellent article by @sharifsbeat : Why The Hipsters Recompose Everything

Recomposing Redux

So like I said in the beginning of this post, I was playing around with Recompose and I thought « Hey this recompose thing is tight! I’ll try to build Redux clone with it »

Let me show what I ended up building :)

createStore

In Redux, the createStorefunction creates a redux store.

It takes an initialState and a reducer and returns a new store :

const createStore = (initialState, reducer) => {
  let state = initialState
  return {
    getState: () => state,
    subscribe: cb => {},
    dispatch: action => {
      state = reducer(state, action)
    }
  }
}

Here’s a basic redux store.

Now let’s add an event emitter to refresh all connected components when the state changes.

To do so, I used mitt from @_developit

import mitt from 'mitt'

const createStore = (initialState, reducer) => {
  // a new event emitter instance
  const emitter = mitt()
  let state = initialState
  return {
    getState: () => state,
    subscribe: cb => {
      // subscribe to "update" event
      emitter.on('update', cb)
      return () => emitter.off('update', cb)
    },
    dispatch: action => {
      state = reducer(state, action)
      // trigger an "update" event
      emitter.emit('update', state)
    }
  }
}

That’s it! We now have a createStorefunction ready!

The Provider component

The Providercomponent in Redux takes the redux store in its props and pass it to all connected components through the Context API of React.

We will need to use the withContextfunction from recompose :

import React from 'react'
import { withContext } from 'recompose'

import PropTypes from 'prop-types'

// defining contextTypes is mandatory
const contextTypes = { store: PropTypes.object }

const Context = withContext(contextTypes, props => ({
  store: props.store
}))

const Provider = Context(({ children }) => <div>{children}</div>)

We take the redux store from the props of Provider and we put it under the 'store' key of the context object

react-redux connect

The connect function from 'react-redux' takes 2 functions mapStateToProps and mapDispatchToProps an returns a HOC .

This HOC takes care of :

  1. getting the store from the context
  2. passing down the state from the store to the component
  3. subscribing to store changes

For each of these steps, we will use a dedicated recompose helper :

  1. getting the store from the context -> getContext
  2. passing down the state from the store to the component -> withState
  3. subscribing to store changes -> lifecyle

Now let’s compose them using the compose function provided in 'recompose'!

import React from 'react'
import { compose, withState, getContext, lifecycle } from 'recompose'

import PropTypes from 'prop-types'

// defining contextTypes is mandatory
const contextTypes = { store: PropTypes.object }

const enhance = compose(
  // 1. getting the store from the context
  getContext(contextTypes),

  // 2. passing down the state from the store to the component
  //    using the 'state' prop
  withState('state', 'setStoreState', {}),

  // 3. subscribing to store changes
  lifecycle({
    componentDidMount() {
      const { store, setStoreState } = this.props
      setStoreState(store.getState())
      this.unsub = store.subscribe(newState => {
        setStoreState(newState)
      })
    },
    componentWillUnmount() {
      if (this.unsub) {
        this.unsub()
        this.unsub = null
      }
    }
  })
)

Now we can create our connect function :

const connect = (mapStateToProps, mapDispatchToProps) => Component =>
  enhance(({ store, state, ...ownProps }) => {
    const derivedState = mapStateToProps
      ? mapStateToProps(state, ownProps)
      : state
    const dispatchers = mapDispatchToProps
      ? mapDispatchToProps(store.dispatch, ownProps)
      : store.dispatch
    return <Component {...derivedState} {...dispatchers} {...ownProps} />
  })

Done!

We have everything in place now in our redux clone.

Let's not forget that the redux codebase has many more checks and optimizations that this simple implementation to prevent unnecessary re-renders when calling store.dispatch()

Using our « recomposed » redux

Let’s create a reducer, an initial state and a store :

const reducer = (state, action) => {
  const value = action.value || 1
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + value }
    case 'DECREMENT':
      return { ...state, count: state.count - value }
    default:
      return state
  }
}

const initialState = { count: 0 }
const store = createStore(initialState, reducer)

Here are our mapStateToProps and mapDispatchToProps functions :

const mapStateToProps = (state, ownProps) => ({
  num: state.count
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  increment: () => dispatch({ type: 'INCREMENT' }),
  increment4: () => dispatch({ type: 'INCREMENT', value: 4 }),
  decrement: () => dispatch({ type: 'DECREMENT' })
})

And our App component that we will wrap with connect(mapStateToProps, mapDispatchToProps) :

const App = ({ title, increment, increment4, decrement, num }) => (
  <div>
    <h2>{title}</h2>
    <p>Count : {num}</p>
    <button onClick={increment}>+1</button>
    <button onClick={decrement}>-1</button>
    <button onClick={increment4}>+4</button>
  </div>
)

const CounterApp = connect(mapStateToProps, mapDispatchToProps)(App)

Let’s boot everything up with the Provider component we created before

ReactDOM.render(
  <Provider store={store}>
    <CounterApp title="Counter" />
  </Provider>,
  document.getElementById('root')
)

As you can see, we use it almost exactly like the original redux!

(I said almost because I did not create a combineReducers function and split the store in sub-stores like redux does with combineReducers - maybe I'll update this article if I do it)

The end result is available here : Edit Functional Redux 😎



author undefined

By Yannick Spark : a Front-end engineer who works remotely at Teacup Analytics.
He likes Functional programming, Nutrition & Fasting, and Remote work.
You should definitely on Twitter 👋