Currying Action Creators in React Redux

May 7, 2018


I ran into a situation where I wanted to curry an action creator. Without giving it much thought, I gave the following a shot

const addNumber = (id) => () => ({ type, payload: { id } });

which I passed to my component through mapDispatchToProps through the connect higher order component. This didn't work. After a few minutes I realized my curried function was being wrapped by dispatch and I wouldn't be able to partially apply it like I wanted to. You can read more about how mapDispatchToProps works here: https://github.com/reactjs/react-redux/blob/master/docs/api.md

The trick, I realized, was to create the curried function after the action creator had been wrapped by dispatch.

My contrived example

I wrote the following application based on a create-react-app starter project. To replicate the same locally just follow these quick steps:

  1. create-react-app curry && cd curry

  2. yarn add redux react-redux

  3. yarn start

and then paste the following code over path/to/curry/src/App.js:

import React, { Component } from 'react';
import { connect, Provider } from 'react-redux';
import { createStore } from 'redux';

const initialState = {
  item0: 0,
  item1: 0,
  item2: 0,
  item3: 0,
  item4: 0,
  item5: 0,
}

const myNumber = (state, {type, payload}) => {
  switch(type) {
    case 'ADD':
      return {
        ...state,
        [payload.itemId]: state[payload.itemId] + 1,
      }
    default:
      return state;
  };
};

const store = createStore(myNumber, initialState);

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <ListContainer />
      </Provider>
    );
  }
}

export default App;

class ListContainer extends Component {
  constructor(props) {
    super(props);
    const { addNumber } = this.props;
    // In the constructor so a new object instance of this function is not created every render
    this.curriedAddNumber = (itemId) => () => addNumber(itemId);
  }
  render() {
    const { items } = this.props;
    const itemKeys = Object.keys(items);
    return (
      <div>
        {
          itemKeys.map((itemId, index) => (
            <ListItem
              key={itemId}
              number={items[itemId]}
              addNumber={this.curriedAddNumber(itemId)}
            />
          ))
        }

      </div>
    );
  }
}
const mapStateToProps = (state) => ({ items: state });

const addNumber = (itemId) => ({
  type: 'ADD',
  payload: {
    itemId
  }
});

ListContainer = connect(
  mapStateToProps,
  { addNumber },
)(ListContainer)

const style = {
  container: {
    display: 'flex',
    border: '1px solid black',
    padding: 5,
    margin: 5,
  },
  num: {
    flex: 4,
    textAlign: 'center',
  },
  button: {
    flex: 1
  },
};

const ListItem = ({ addNumber, number, id }) => (
  <div style={style.container}>
    <div style={style.num}>{number}</div>
    <button
      style={style.button}
      onClick={addNumber}
    >
      Add Number
    </button>
  </div>
);

Explanation

In this example, curriedAddNumber is created after addNumber is wrapped by dispatch by mapDispatchToProps. This allows me to partially apply curriedAddNumber as I originally intended.

This might seem more complicated than necessary, but take a moment to think of the implications. Now my child component has a function to add a number to the redux state, but doesn't even know which ListItem it is! The example I ran into and puzzled through this was quite a bit more complicated, but if you googled the question and found the title, then I imagine you should be able to take it from here :). Please comment if you have any questions.

Cheers

Comment Enter a new comment: