I have a lot of actions of the form: LOAD_XYZ
, LOAD_XYZ_FAIL
, LOAD_XYZ_SUCCESS
for different page elements that need to be loaded.
I frequently wish to perform a redirect ( react-router
) after some item gets loaded, but since redux-observable
does not return a promise I cannot perform the redirect in my component - but instead I have to do it using redux
and push('...')
from react-router-redux
.
That unfortunately leads to a lot of duplication as I have LOAD_XYZ
and LOAD_XYZ_REDIRECT_TO
versions of the same action, plus two epics for each.
This is starting to feel wasteful and with too much redundancy
const fetchUserEpic = action$ =>
action$.ofType(FETCH_USER)
.mergeMap(action =>
ajax.getJSON(`https://api.github.com/users/${action.payload}`)
.map(response => fetchUserFulfilled(response))
);
const fetchUserAndRedirectEpic = action$ =>
action$.ofType(FETCH_USER_AND_REDIRECT)
.mergeMap(action =>
ajax.getJSON(`https://api.github.com/users/${action.payload}`)
.concatMap(response => [fetchUserFulfilled(response), push(action.redirectTo)])
);
Is there some sort of pattern/approach where I can sequence actions to be dispatched after some kind of async request has completed, to avoid redundancy and having to implement multiple versions of the same epic.
For example, I would like instead to have a separate REDIRECT_TO
action that I can sequence after the item was loaded.
Something like these (imaginary) solutions:
dispatch(fetchUser(...)).then(redirectTo("..."))
dispatchSequence(fetchUser(...), redirectTo("..."))
I know redux-thunk
can do this but then I miss out on all the rxjs operators.
I'm probably trenching into dangerous grounds but I did find a way to do this.
First we make a middleware which will add a completion observable as metadata to actions.
import { Subject } from "rxjs";
export const AddListenerMiddleware = store => next => action => {
const forkedAction = Object.assign({}, action, { meta: { listener: new Subject()}});
next(forkedAction);
return forkedAction.meta.listener.asObservable();
};
Since this is a Subject
we can call next
on it to emit some value. Creating a notify
helper allows us to notify any subscribers of when the action has completed (plus pass down the data).
// notify doesn't consume but just emits on the completion observable
const notify = (action, fn) => source =>
Observable.create(subscriber =>
source.subscribe(emmission => {
if (action.meta && action.meta.listener) {
action.meta.listener.next(fn(emmission));
}
subscriber.next(emmission);
})
);
const fetchUserEpic = action$ =>
action$.ofType(FETCH_USER)
.mergeMap(action =>
ajax.getJSON(`https://api.github.com/users/${action.payload}`)
.map(response => fetchUserFulfilled(response))
.pipe(notify(action, response => response.data))
);
And now our dispatch returns an observable on which we can do standard rxjs operations. So this finally this becomes possible:
dispatch(fetchUser(...))
.subscribe(({id}) => dispatch(redirectTo("/" + id)))
Disclaimer: Use at your own risk!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.