-
Notifications
You must be signed in to change notification settings - Fork 2
Recipes
There are two scenarios where you may want to write code targeting facets in your sagas:
- Your saga emits some number of 'result' actions based on a trigger action, and you want those actions to have the same facet metadata as the original trigger action
- You want your saga to listen to only actions from a particular facet
redux-facet
includes a built-in tool for applying the same facet metadata to 'outgoing' actions from a saga as the one that triggered it. Read the docs for usage and an example.
To listen to actions which come from a specific facet, utilize redux-saga
's function-as-pattern functionality.
const isProfileGetUserAction = action =>
action.type === 'GET_USER' &&
hasFacet('profile')(action);
const watchProfileGetUser = function*() {
yield takeEvery(isProfileGetUserAction, getUserHandlerSaga);
}
Redirecting to a new route after a successful asynchronous multi-action operation was one of the original guiding problems for redux-facet
. To set up the problem, let's suppose our application enables a user to create and update Posts. When a user edits a post at /posts/:postId/edit
and clicks "Save", they are redirected to /posts/:postId
. With typical sagas, this is fairly simple (assuming we have react-router-redux ready):
const handlePostUpdateSaga = function*(action) {
const response = yield call(api.updatePost, action.payload);
yield put(postActions.update.complete(response));
// redirect the user
yield put(routerActions.push, `/posts/${action.payload.id}`);
}
const watchPostUpdate = function*() {
yield takeLatest('POST_UPDATE', handlePostUpdateSaga);
}
This makes sense, since our router state is stored in Redux, and modifying our route should go through actions. The code is easy to read.
However, let's suppose we also want to add the ability for a user to quick-edit a post they've made as they're scrolling through their feed on /feed
. Perhaps when they come across their own post, they can click a button and edit the post inline and save it without leaving the feed. The problem is, if we reuse our UPDATE_POST
action, we will trigger our redirect logic, and the user will be pulled away from their feed and back to /posts/:postId
when the update completes.
In traditional Redux, we would be forced to create a new action type, like FEED_UPDATE_POST
, which does the exact same thing as UPDATE_POST
, but avoids tripping the redirect logic.
With redux-saga
, we can stick to DRY principles and reuse all our existing actions, sagas, and reducers. First, we apply a facet to both the edit post page, and the feed.
// containers/EditPost.js
export default compose(
connect(mapStateToProps),
facet('editPost', mapDispatchToProps),
)(EditPostForm);
// containers/Feed.js
export default compose(
connect(mapStateToProps),
facet('feed', mapDispatchToProps),
)(FeedView);
Now outgoing UPDATE_POST
actions will have facet metadata for their source facet. We then apply facetSaga
to our handlePostUpdateSaga
and remove the redirect logic:
// only common logic for all post update operations goes here
const handlePostUpdateSaga = function*(action) {
const response = yield call(api.updatePost, action.payload);
yield put(postActions.update.complete(response));
}
const watchPostUpdate = function*() {
yield takeLatest('POST_UPDATE', facetSaga(handlePostUpdateSaga));
}
Finally, we create a new saga to handle our redirect logic, listening only for the editPost
facet, not the feed
facet.
// sagas/updatePost/redirect.js
const handleEditPostUpdateComplete = function*(action) {
yield put(routerActions.push, `/posts/${action.payload.id}`);
}
const isEditPostUpdateCompleteAction = action =>
action.type === 'POST_UPDATE_COMPLETE' &&
hasFacet('editPost')(action);
const watchEditPostUpdateComplete = function*() {
yield takeLatest(isEditPostUpdateCompleteAction, handleEditPostUpdateComplete);
}
Our new saga will only trigger on the resulting POST_UPDATE_COMPLETE
action which came from the editPost
facet.
While this approach may seem to take a longer, more verbose code path than other solutions, it has some advantages:
- Complete code reuse: actions, reducers and sagas for updating a post are reused, and an extra 'cluttering' action is not created for specific use cases.
- Use-case-specific paths are separated from the main, generic code. This makes them easy to discover by other developers, easy to delete if no longer needed, or to update if product needs change without touching core shared logic.