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!
The following sites were very useful when figuring out how to make this work: