One of the top complaints among people using and learning Redux is “all the boilerplate.” And they’re not wrong… you really can end up with a lot of code across a lot of files – action constants, action creators, reducers, initializing it all, and then wiring up the components with mapStateToProps and mapDispatchToProps.
That last one, though, mapDispatchToProps
, is what I want to talk about here. Specifically – why you might not need that function, and what you can replace it with instead.
Mark Erikson a.k.a @acemarke gave a semi-impromptu talk at the React Boston conference where he mentioned this feature, and minds were blown. (his slides are here)
Discovered it recently and removed oodles of dispatch(blah()) statements. So much easier!
Literally worth the purchase ticket price for the whole conference.
So without further ado, let’s get into it.
React Redux and connect
The react-redux
library supplies a function called connect
, which is how you can feed data from Redux’s store into your React components. If you’re reading this, I assume you already knew that – but if not, take a few minutes and read through this Redux tutorial (there’s also a video) to get the lay of the land.
The connect
function is commonly passed 1 or 2 arguments:
First, a mapStateToProps
function that plucks pieces of state out of Redux and assigns them to props that your React component will use.
And often a second argument: a mapDispatchToProps
function which binds action creator functions so that you don’t have to write props.dispatch(actionName())
all over the place, you can just write props.actionName()
.
(connect
actually takes up to 4 arguments but we’re only talking about the first two here)
Without mapDispatchToProps
Here’s a common example: a simple component, connected to Redux, which needs to trigger 3 different actions depending on which button the user clicks – increment, decrement, and reset.
(See the full working code, reducer and all, in this CodeSandbox)
import React from "react";
import { connect } from "react-redux";
import { increment, decrement, reset } from "./index";
function Counter({ count, dispatch }) {
return (
<div>
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(reset())}>reset</button>
</div>
);
}
const mapStateToProps = state => ({
count: state.count
});
export default connect(mapStateToProps)(Counter);
Notice that the component receives a dispatch
prop, which comes from connect()
, and then has to use it directly to trigger the actions.
With mapDispatchToProps as a function
You can avoid having to call action creators with dispatch
by passing a second argument to connect, the mapDispatchToProps
function.
Here’s the above component rewritten with that function (but don’t stop here! keep reading! it gets better).
import React from "react";
import { connect } from "react-redux";
import { increment, decrement, reset } from "./index";
function Counter({ count, increment, decrement, reset }) {
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={reset}>reset</button>
</div>
);
}
const mapStateToProps = state => ({
count: state.count
});
const mapDispatchToProps = dispatch => ({
decrement: () => dispatch(decrement()),
increment: () => dispatch(increment()),
reset: () => dispatch(reset())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
(Full code for this post is in this CodeSandbox)
There are 2 major changes here.
First, the dispatch
function is no longer being passed in as a prop, and the 3 action functions have taken its place. Since they’re already bound, they can be passed as-is to the button onClick
handlers, which makes the code a bit nicer to read.
Second, you can see the new mapDispatchToProps
function which is basically doing the work that we previously had to do in the onClick
handlers. It takes the dispatch
function as an argument, and returns an object where each property gets passed into the component as a prop.
Now let’s streamline it further.
With mapDispatchToProps as an object
One little-known feature of React-Redux is that mapDispatchToProps
can just be a plain object. It doesn’t have to be a function.
I say “little-known” because I’ve seen people blown away by how simple this can be… but then, some people seem to have learned this on Day 1 and figure everyone else knows about it 🤷
Maybe mapDispatchToProps isn’t a good name at this point. Maybe you’d rather call it “actionCreators”, or skip naming it entirely and just pass an object literal into connect
. Either way, this is what it looks like:
import React from "react";
import { connect } from "react-redux";
import { increment, decrement, reset } from "./actions";
function Counter({ count, increment, decrement, reset }) {
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={reset}>reset</button>
</div>
);
}
const mapStateToProps = state => ({
count: state.count
});
// Look how simple this is now!
const mapDispatchToProps = {
decrement,
increment,
reset
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
(Full code is in the same CodeSandbox)
The way this works is that each property of the object is expected to be an action creator function (in other words, a function that returns an action). Then, connect
will go through and automatically wrap (bind) each of those functions in a call to dispatch
, like we did manually before with dispatch(increment())
.
One caveat, which may or may not matter to you, is that passing one of these bound action creators as an event handler (like onClick
) means that it’ll receive the “event” as an argument. If your actions don’t accept arguments, this is fine. But if you want to ignore the “event” for some reason, you’ll have to write wrapper functions to do that.
Customize The mapDispatchToProps Object
If your actions do need arguments, then you can just wrap those specific action creators in a function, like so:
const mapDispatchToProps = {
decrement,
increment: () => increment(42),
reset
};
And, of course, you can always fall back to passing a function as mapDispatchToProps
. But for the rest of the time, save yourself some typing and use the object form 👍