mapDispatchToProps
Okay, so we have our replacement for useSelector
, how do we pass dispatch
in?
In order to update the state—and subsequently the UI, we're going to need to do a few things.
- We need an action to dispatch.
- We need the reducer to deal with that action.
- We need the
NewItemForm
to dispatch that action.
We'll use the aciton creator pattern to format our action for us in action.js
.
export const ITEM_ADDED = "ITEM_ADDED";
export const addNewItem = (name, price) => {
return {
type: ITEM_ADDED,
payload: {
name,
price,
},
};
};
You'll notice what I don't have in here: anything the that we didn't didn't get from the action itself. So, like, we don't know the uuid
yet. The quantity will be 1 by default. The only things that the user gave us in that form was the name
and the price
and our action reflects that.
Next, we'll update the reducer.
export const reducer = (state = initialItems, action) => {
if (action.type === ITEM_ADDED) {
const item = { uuid: id++, quantity: 1, ...action.payload };
return [...state, item];
}
return state;
};
Now, one thing you'll notice is that I will let the action payload override the default properties. Maybe in the future, we add a quantity field. This will take the quantity from the action and use that if one exists.
Let's try out firing an action from the developer tools.
{
type: 'ITEM_ADDED',
payload: { name: 'Braised Seitan', price: 12 }
}
Cool, we're most of the way there. Now we just need to wire that up with the NewItemForm
and we're good to go.
We can't just require the action creator in the component because it's just a function that returns an object and it doesn't know anything about dispatch
.
What we want to do is pass in an onSubmit
prop, which the component is already expecting that is bound to Redux's dispatch
.
Let's start with the simplest possible version in containers/NewItemFormContainer.js
:
import { connect } from "react-redux";
import { NewItemForm } from "../components/NewItemForm";
export const ConnectedNewItemForm = connect()(NewItemForm);
Connect components received dispatch
out of the box. So, now we can do something like this:
export const NewItemForm = ({ onSubmit, dispatch }) => {
// …
const handleSubmit = (event) => {
event.preventDefault();
if (typeof onSubmit === "function") {
onSubmit(event, { name, price });
}
dispatch(addNewItem(name, price));
setName("");
setPrice(0);
};
// …
};
(We'll also want to swap out NewItemForm
for NewItemFormContainer
in Calculator.js
.)
This approach is a bit flawed. It ties our presentational component to Redux, which is less than optimal. It doesn't create a clear API contract. NewItemForm
can literally dispatch anything it wants. We can do better.
Just like we can format our state to the props of a presentation component. We can do that with dispatch
as well.
import { connect } from "react-redux";
import { NewItemForm } from "../components/NewItemForm";
import { addNewItem } from "../store/items/reducer";
const mapDispatchToProps = (dispatch) => {
return {
onSubmit: (name, price) => dispatch(addNewItem(name, price)),
};
};
export const NewItemFormContainer = connect(
null,
mapDispatchToProps
)(NewItemForm);
We can rip out that fun stuff we did with dispatch
and put the component back to the way we found it.
Let's say we had a whole bunch of actions. We probably don't need to call each one with dispatch
by hand. We can use bindActionCreators
in order to take an object of action creators and spit out an object with all of those aciton creators bound to dispatch
.
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{
onSubmit: addNewItem,
},
dispatch
);
};
For simple things, we can also use a simpler syntax.
const mapDispatchToProps = {
onSubmit: addNewItem,
};
If connect
receives an object, it will automatically pass it to bindActionCreators
and pass it through to the component.