@kempsterrrr

Using Higher-order components with React and Redux

July 17, 2018

React-redux provides a connect() component which allows us to wrap existing React components as they are exported, injecting data from the Redux store as well as actions (functions) you can dispatch to make changes to that store. This uses the Higher-Order Component pattern which means it is a function that takes a component as an argument like so connect(someComponent) and returns a new component with additional properties.

Whilst building footnote we found this pattern especially powerful. Each part of the app needed specific data and actions relevant to the job it was created to perform. Using the connect() component alone you need to hard-code the dispatch actions and props for each component to explicitly tell redux what you need.

E.g. UserProfile - (simplified version)

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchArticles } from '../actions/article-actions'

class Articles extends Component {
  render() {
    const { articles, deleteArticle } = this.props;
      return (
        <div className="articles-container" >
          {articles.reading.map(item => {
            return (
              <Article
                key={item.id}
                {...item}
              />
            );
          })}
        </div>
      );
    }
  }
}

const mapStateToProps = state => {
  return {
    ...state.articles
  }
}

const mapDispatchToProps = dispatch => {
  return {
    fetchArticles(url, userId) {
      dispatch(fetchArticles(url, userId));
    },
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Articles)

Having to repeat yourself sucks. Using connect this way means you have to write the mapStateToProps and mapDispatchToProps functions out every time you want use data or actions from redux. Luckily, we can steal the HOC concept and extract the logic into another HOC component which we import and use in any other part of the app.

Here is a simplified version of the withArticles() HOC we created for footnote:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchArticles } from '../actions/article-actions'

export function withArticles(WrappedComponent) {
  class withArticlesComponent extends Component {
    render() {
      return <WrappedComponent {...this.props} />
    }
  }

  const mapDispatchToProps = dispatch => {
    return {
      fetchArticles(url, userId) {
        dispatch(fetchArticles(url, userId))
      },
    }
  }

  const mapStateToProps = state => {
    return {
      ...state.articles,
    }
  }

  return connect(
    mapStateToProps,
    mapDispatchToProps
  )(withArticlesComponent)
}

Rather than repeat this code everywhere it allowed us to wrap any component and get the articles data and actions for free like so:

export default withArticles(Articles)

Not only did this cut down the size of our component files it also allowed members of the team less experienced in React and Redux to quickly try ideas without having to worry about the wiring required the get the data and actions they needed. As amatuers pushing for a quick MVP this was invaluable.

We did however notice some drawbacks. In some components we were ‘chaning’ many higher order components together e.g. withRouter(withUser(withArticles(Header))) which isn’t the prettiest code as well as instances where we were passing much more data than we needed to some components. The latter is arguably down to bad practice around not making effective use of React’s built into features for passing data but it did sometimes make debugging harder as there was more to data being passed to consider.

All in all we saw huge value in Higher Order Components and would use them again however we’d be careful not to use them to hammer in screws.


Will Kempster

Recruiting JavaScript engineers in London whilst learning JavaScript engineering in London. Normally talking about one, or the other, in London. Find me on twitter and Github 👍