Feb 26, 2018

Using mergeProps in a redux container

react redux

I recently had a situation where I was creating multiple Redux containers that wrapped the same component. In one particular case the action creator I was passing in through mapDispatchToProps required a different number of parameters than the underlying React component expected. I didn’t want to duplicate that React component to account for the difference … there had to a better a way!

This may be a little contrived, but assume that we have a React component that is importing 2 Redux containers like so:

components/List.jsx

import OperationItem from "../containers/OperationItem"
import OperationAddonItem from "../containers/OperationAddonItem"

export const List = ({operationId, addonId}) => {
  <OperationItem id={operationId} />
  <OperationAddonItem operationId={operationId} id={addonId} />
}

These Redux containers wrap the following React component which dispatches an action when clicked:

components/Item.jsx

export const Item = ({id, addOperation}) => {
  return (
    <a onClick={() => addOperation(id)}>
      Add Item!
    </a>
  )
}

In this particular case addOperation can be mapped to one of the following action creators:

actions.js

export const addOperation = (id) => {
  return { type: "ADD_OPERATION", payload: id }
}

export const addOperationAddon = (operationId, id) => {
  return { type: "ADD_OPERATION_ADDON", payload: { operationId, id } }
}

The first Redux container is pretty straightforward:

containers/OperationItem.jsx

import Item from "../components/Item"
import { connect } from "react-redux"
import { addOperation } from "../actions"

// { id } is just destructing of the `ownProps` parameter
const mapStateToProps = (state, { id }) => {
  return {
    id
  }
}

const mapDispatchToProps = dispatch => {
  updateOperation: (id) => dispatch(updateOperation(id))
}

export default connect(mapStateToProps, mapDispatchToProps)(Item)

In the second case we don’t want to require the Item component to be aware of the operationId prop. So the container should take care of setting this.

containers/OperationAddonItem.jsx

import Item from "../components/Item"
import { connect } from "react-redux"
import { addOperationAddon } from "../actions"

// { id } is just destructing of the `ownProps` parameter
const mapStateToProps = (state, { id }) => {
  return {
    id
  }
}
const mapDispatchToProps = dispatch => {
  addOperation: (operationId, id) => dispatch(addOperationAddon(operationId, id))
}

export default connect(mapStateToProps, mapDispatchToProps)(Item)

However this will NOT work since addOperation only provides 1 parameter (the id). This is where mergeProps comes in. You’ll need to make the following modifications:

const mapDispatchToProps = {
  dispatchAddOperation: (operationId, id) => dispatch(addOperationAddon(operationId, id))
}

// { id, operationId } is just destructing of the `ownProps` parameter
const mergeProps = (propsFromState, propsFromDispatch, {id, operationId}) => {
  return {
    id,
    addOperation: (id) => propsFromDispatch.dispatchAddOperation(operationId, id)
  }
}

export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Item)

And voila, addOperation will automatically set the operationId parameter and the underlying Item component will never need to know about it!

Resources

The following sites were very useful when figuring out how to make this work: