Reputation: 1578
I'm thinking through some scenarios surrounding redux and I wasn't able to find a clean solution to this example:
Let's say you have a component that is a list of recipes. When you select a recipe from that list, you dispatch(RECIPE_SELECTED)
. That async action-creator may do additional related to the recipe - perhaps go async fetch the ingredients for the recipes, save the selection to the server, whatever.
In a totally separate component you have a list of professional chefs. The desired behavior is that when a user selects a recipe, you populate the list of professional chefs with any chef who has a variation of the selected recipe.
How do you listen for RECIPE_SELECTED
and then dispatch a totally unrelated action that has a dependency on the recipe? Something like...
when RECIPE_SELECTED:recipe
loadChefs(recipe).then(res => dispatch(CHEFS_LOADED, res.chefs))
You could mix this loadChefs/dispatch
into the action-creator of RECIPE_SELECTED
, but that's a really gross mixing of concerns and will quickly weave a tangled web.
You could also do some very imperative (i.e. against the grain for redux) stuff like so (using React):
componentWillReceiveProps(nextProps) {
if (nextProps.recipe !== this.props.recipe) {
const { dispatch, recipe } = nextProps
dispatch(loadChefs(recipe))
}
}
I really don't like either of these solutions. Thoughts?
Upvotes: 1
Views: 1157
Reputation: 24818
Are you familiar with redux-thunk? https://github.com/gaearon/redux-thunk
With redux-thunk applied as middleware, you can do something like this:
function selectRecipe(recipe) {
return function (dispatch) {
dispatch(setRecipe(recipe));
return loadChefs(recipe).then((res) =>
dispatch(setChefs(res.chefs))
);
};
}
Where setRecipe
and setChefs
are simple action creators. e.g.
function setRecipe(recipe) {
return {
type: SET_RECIPE,
recipe
};
}
function setChefs(chefs) {
return {
type: SET_CHEFS,
chefs
};
}
I recommend reading the docs on Async Actions. https://redux.js.org/advanced/async-actions
Upvotes: 4
Reputation: 3324
A different solution is to use the Redux-Saga middleware. This lets you write something like this:
function* loadChefsSaga() {
# takeLatest sets up a system that spawns the inner generator every time
# an action matching the pattern is dispatched. If the inner generator
# is still running when additional actions are dispatched, it is cancelled,
# and a new one is spawned.
yield takeLatest('RECIPE_SELECTED', function* (recipe) {
# When a Promise is yielded, the generator is resumed when the Promise
# resolves. Alternatively, if it rejects, the rejected value is thrown
# into this generator.
const {chefs} = yield loadChefs(recipe)
# Assuming chefsLoaded is an action creator for CHEFS_LOADED
# `put` is redux-saga's equivelent of `dispatch`. We use it because
# the saga doesn't have direct access to the `dispatch` function.
yield put(chefsLoaded(chefs))
})
}
I'm assuming that you have a basic familiarity with how javascript generators work. If not, go look them up; they're a powerful pattern. In this case, redux-saga uses them to construct functions that can block on things. Every time something is yielded, redux-saga treats it as an "Effect" that it knows how to process. For instance, when a Promise
is yielded, redux-saga sets it up so that the generator is resumed when the Promise resolves (or something is thrown into the generator if it rejects).
Upvotes: 0