How to reinit actions in redux-observable? - redux

For example, this code this jsbin example:
const pingEpic = action$ =>
action$.ofType(PING)
.delay(1000) // Asynchronously wait 1000ms then continue
.mapTo({ type: PONG })
.takeUntil(action$.ofType(CANCEL));
When I use takeUntil as above, after dispatching the CANCEL action and a delay of 1 second, the action never fires again. Why?

The problem is a subtle but critical misunderstanding of how RxJS works--but fear not, this is very common.
So given your example:
const pingEpic = action$ =>
action$.ofType(PING)
.delay(1000)
.mapTo({ type: PONG })
.takeUntil(action$.ofType(CANCEL));
This epic's behavior can be described as filtering out all actions that don't match type PING. When an action matches, wait 1000ms and then map that action to a different action { type: PONG }, which will be emitted and then dispatched by redux-observable. If at any time while the app is running someone dispatches an action of type CANCEL, then unsubscribe from the source, which means this entire chain will unsubscribe, terminating the epic.
It might be helpful to see how this looks if you did it imperatively:
const pingEpic = action$ => {
return new Rx.Observable(observer => {
console.log('[pingEpic] subscribe');
let timer;
const subscription = action$.subscribe(action => {
console.log('[pingEpic] received action: ' + action.type);
// When anyone dispatches CANCEL, we stop listening entirely!
if (action.type === CANCEL) {
observer.complete();
return;
}
if (action.type === PING) {
timer = setTimeout(() => {
const output = { type: PONG };
observer.next(output);
}, 1000);
}
});
return {
unsubscribe() {
console.log('[pingEpic] unsubscribe');
clearTimeout(timer);
subscription.unsubscribe();
}
};
});
};
You can run this code with a fake store here: http://jsbin.com/zeqasih/edit?js,console
Instead, what you usually want to do is insulate the subscriber chain you want to be cancellable from the top-level chain that is suppose to listen indefinitely. Although your example (amended from the docs) is contrived, let's run through it first.
Here we use the mergeMap operator to let us take the matched action and map to another, separate observable chain.
Demo: http://jsbin.com/nofato/edit?js,output
const pingEpic = action$ =>
action$.ofType(PING)
.mergeMap(() =>
Observable.timer(1000)
.takeUntil(action$.ofType(CANCEL))
.mapTo({ type: PONG })
);
We use Observable.timer to wait 1000ms, then map the value it emits (which happens be to be the number zero, but that's not important here) to our PONG action. We also say we want to "take" from the timer source until either it completes normally or we receive an action of type CANCEL.
This isolates the chains because mergeMap will continue to subscribe to the observable you return until it errors or completes. But when that happens, it doesn't itself stop subscribing to the source you applied it to; the action$.ofType(PING) in this example.
A more real-world example is in the redux-observable docs in the Cancellation section
Here we placed the .takeUntil() after inside our .mergeMap(), but after our AJAX call; this is important because we want to cancel only the AJAX request, not stop the Epic from listening for any future actions.
const fetchUserEpic = action$ =>
action$.ofType(FETCH_USER)
.mergeMap(action =>
ajax.getJSON(`/api/users/${action.payload}`)
.map(fetchUserFulfilled)
.takeUntil(action$.ofType(FETCH_USER_CANCELLED))
);
This all may sound confusing, but like most powerful things, once you get it, it'll become intuitive. Ben Lesh does an excellent job of explaining how Observables work in his recent talk, including discussing how operators are a chain of Observables and even about isolating subscriber chains. Even though the talk is at AngularConnect, it's not Angular specific.
As an aside, it's important to note that your epics do not swallow or otherwise prevent actions from reaching the reducers, e.g. when you map an incoming action to another, different action. In fact, when your epic receives an action, it has already been through your reducers. Think of your epics as sidecar processes that listens to a stream of your apps actions, but can't prevent normal redux things from happening, it can only emit new actions.

Related

Post request redux thunk

I have GET requests and normally when those succeeded I save data in store, but for POST requests I need to know if it succeeded or not, in order to execute some code (show a message and redirect), the docu says you can use an isLoading variable, but it just says if the service is working but not if it succeeded, if I try to create a new success variable in the store, it will be turned on forever after the request and I don't need that either. I tried returning a promise from the action creator and handle response directly inside the component but it looks like the same to call axios there instead of using redux.
My action creator looks like this:
export function createProject(userId, projectName) {
return function (dispatch) {
dispatch({ type: projectsActions.START_CREATE_PROJECT });
return ProjectsService.createProject(userId, projectName).then(() => {
dispatch({ type: projectsActions.SUCCESS_CREATE_PROJECT });
}).catch((error) => {
dispatch({ type: projectsActions.ERROR_CREATE_PROJECT });
throw error;
});
}
}
I understand where your doubts are coming from, it doesn't seem appropriate to have a field on your Redux store only to know the success of a one-time request.
If you only need to make a post request and only care about it's result once, the simplest way to do it is to use state in the component making the request. Component-level state is easily manageable and gets removed from memory when the component is unmounted, but on the other hand you may want to have a single source of truth for your app. You have to make a choice, but your Redux implementation is correct.

Await the asynchronous dispatch of an action in redux-observable

I have an epic which dispatches an action that is handled by a redux middleware, which returns a promise once the action is dispatched. The epic looks approximately like this:
const myEpic = action$ =>
action$.pipe(
ofType(SAVE_ACTION),
switchMap(action => [
saveData(action.payload)
])
)
When the SAVE_ACTION is dispatched, it is picked up by the epic, which dispatches an action created by the saveAction action creator.
This resulting action is intercepted by a redux middleware (specifically redux-axios-middleware) which does an HTTP request and converts the result of dispatching such action to a promise, which has the standard behavior of resolving when the HTTP request succeeds and rejecting when the HTTP request fails.
What I would need to do is do additional work once that promise is resolved, within this epic, because I need data contained both in the payload of the initial SAVE_ACTION action and the payload of the HTTP response.
Because the middleware also dispatches actions upon completion of the underlying HTTP request, it would be easy to write an additional epic to do the additional work I need to do in a distinct epic, but I wouldn't have access to the payload of the initial SAVE_ACTION anymore, so I'm trying to figure out if this can be handled all within a single epic, and I haven't found a way so far. I have found online posts like this one, which is very informative, but still doesn't address the issue of awaiting the dispatch of an action, rather than a plain observable.
One approach would be to use merge like this:
import { merge } from 'rxjs/observable/merge';
const myEpic = action$ => {
let initialPayload = null;
return merge(
action$.pipe(
ofType(SAVE_ACTION),
switchMap(action => {
initialPayload = action.payload;
return [saveData(action.payload)]
})
),
action$.pipe(
ofType(SAVE_ACTION_SUCCESS),
mergeMap(action => {
// Use initialPayload here.
})
),
)
}

Should "next" always be invoked last in Redux middleware?

tl;dr: Within a Redux middleware function, is it okay to dispatch a new action after calling next to finish updating the store?
I'm building a HackerNews reader using Flutter and built-flutter-redux, based off of Brian Egan's TodoMVC example. It uses HN's Firebase-backed API to pull data:
https://github.com/HackerNews/API
My actions look like this right now:
ActionDispatcher<Null> fetchHackerNewsTopStories;
ActionDispatcher<List<int>> fetchHackerNewsTopStoriesSuccess;
ActionDispatcher<Null> fetchHackerNewsTopStoriesFailure;
ActionDispatcher<Null> fetchNextHackerNewsItem;
ActionDispatcher<HackerNewsItem> fetchHackerNewsItemSuccess;
ActionDispatcher<Null> fetchHackerNewsItemFailure;
There's a piece of middleware that listens for the fetchHackerNewsTopStories action and kicks off a call to the API:
MiddlewareHandler<AppState, AppStateBuilder, AppActions, Null>
createFetchHackerNewsTopStories(HackerNewsRepository service) {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<Null> action) {
service.fetchHackerNewsTopStories().then((ids) {
return api.actions.fetchHackerNewsTopStoriesSuccess(ids);
}).catchError(api.actions.fetchHackerNewsTopStoriesFailure);
next(action);
};
}
When it returns, I update my app's state with the list of IDs.
At some point I need to dispatch another action, fetchNextHackerNewsItem. There's another middleware function that will listen for that action and request the details for the the first story. When those details arrive, it'll request the next story, and so on until everything's updated.
What I'd like to know is whether I can do this:
// Invoked when REST call for the list of top story IDs completes.
MiddlewareHandler<AppState, AppStateBuilder, AppActions, List<int>>
createFetchHackerNewsTopStoriesSuccess() {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<List<int>> action) {
next(action);
api.actions.fetchNextHackerNewsItem(); // Is this cool?
};
}
// Initiates a request for a single story's details.
MiddlewareHandler<AppState, AppStateBuilder, AppActions, Null>
createFetchNextHackerNewsItem(HackerNewsRepository service) {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<Null> action) {
int nextId = api.state.topStoryIds[api.state.loadedUpToIndex];
service.fetchHackerNewsItem(nextId).then((item) {
return api.actions.fetchHackerNewsItemSuccess(item);
}).catchError(api.actions.fetchHackerNewsTopStoriesFailure);
next(action);
};
}
Because createFetchNextHackerNewsItem relies on the app's state (api.state.topStoryIds[api.state.loadedUpToIndex]), I'd like for it to run after the store is updated by the next(action) call.
Is it cool to dispatch new actions in Redux middleware after calling next, or is that some kind of anti-pattern? If it is an anti-pattern, what's the best way to implement this flow?
Yes, it's fine - a middleware can do literally anything it wants when an action is dispatched. That includes modifying / logging / delaying/ swapping / ignoring the original action, as well as dispatching additional actions.

Pause epic when criteria met, then emit buffered action when criteria complete

I am working on an application whereby I we periodically persist information to a server when a user navigates between pages.
We currently do this by scheduling a "persist" action, which propagates a sequenced number of events, before finishing with a "persist_end" action. Currently, if a user navigates quickly, these grouped actions can intercept each other, resulting in various problems. I thought I could buffer the starting action and wait until the ending action was executed.
I've created a similar example using the ping-pong example from Redux-Observables site: https://codepen.io/dualcyclone/pen/GOZRxW?editors=0011
const actionPauser = new BehaviorSubject(false);
const pingEpic = action$ =>
action$.ofType(PING)
.do(action => console.log(action)) // check if action caught by epic
.buffer(actionPauser.asObservable().filter(paused => !paused))
.do(eh => console.log('buffered? ',eh)) // check if buffered actions is occurring
.do(() => actionPauser.next(true)) // tell pauser to pause
.map((buf) => buf[buf.length-1])
.filter(action => action !== undefined)
.delay(1000)
.mapTo({ type: PONG });
const pauseEpic = action$ =>
action$.ofType(PONG)
.delay(1000)
.do(() => actionPauser.next(false)) // tell pauser to not pause
.mapTo({ type: PING });
The premise is similar, I am allowing the user to press the "start PING" button as often as they like, the epic that is listening to this should check to see if there is a ping action currently occuring (by the "actionPauser" BehaviorSubject), and queue any actions until a previous ping action has completed.
The epic should emit the most recent buffered action, so it filters the buffered list then passes through the latest one.
What I can't seem to understand is - the console log to indicate how many buffered actions there are fires as soon as the page loads; which may indicate a problem with the way this is built - am I missing something?
So, whilst the output of the actions isn't exactly desirable (theres not really much I can do about it given the starting action is emitted by a user event), the suggestion Cartant recommended actually does precisely what I need.
Audit:
Ignores source values for a duration determined by another Observable, then emits the most recent value from the source Observable, then repeats this process.
In essence, this allows me to ignore multiple emitted 'PING' events whilst one is currently ongoing. It will then continue the execution of the last most recent 'PING' event, so the output we see is as follows:
(click) PING (click) PING (click) PING (click) PING PONG DONE PONG DONE
The first and last 'PING' actions are the only ones propagated through the Epic, so we see two final PONG actions, both followed by a DONE action.
So, here is the answered example (as also seen on my codepen here)
const pingEpic = action$ =>
action$.ofType(PING)
  .audit(() => actionPauser.filter(paused => !paused))
.do(() => actionPauser.next(true))
.delay(1000)
.mapTo({ type: PONG });
const pauseEpic = action$ =>
action$.ofType(PONG)
.delay(1000)
  .mapTo({ type: DONE })
.do(() => actionPauser.next(false));
Sounds like concatMap might work well in this case.
Projects each source value to an Observable which is merged in the output Observable, in a serialized fashion waiting for each one to complete before merging the next.
So in the case below, only one timer is running at a time. Any PINGs that come in while the previous is still waiting will be buffered.
const pingEpic = action$ =>
action$.ofType(PING)
.concatMap(() =>
Observable.timer(1000)
.mapTo({ type: PONG })
);
https://jsbin.com/gocezut/edit?js,output
(rapidly click four times)
PING
PING
PING
PING
(1000ms pass)
PONG
(1000ms pass)
PONG
(1000ms pass)
PONG
(1000ms pass)
PONG
Remember that most redux-observable questions can be reframed to just be regular RxJS questions, broadening the resources and help you can find. That's the beauty of redux-observable: it's almost entirely just regular RxJS patterns.

Intercepting HTTP Requests in Redux

Many redux examples show making HTTP requests directly in an async action; however this will result in an exterme amount of duplicate code common to all requests, for example:
Retrying failed requests based on status code
Appending common HTTP Headers to each request.
Invoking additional http requests based on response (ie: oauth token refresh)
Aborting in-flight requests on route transitions.
My gut feeling is that a middleware could be used to make http requests (in the same vein as redux-api-middleware) which can keep the in-flight requests in the store - however I am also wondering if I'm leaning on bad habbits - is a little duplication a small price to pay for immutability?
Your action creators are just JavaScript functions.
If you have duplication between several functions, you extract the common code into another function. This is no different. Instead of duplicating the code, extract the common code into a function and call it from your action creator.
Finally, this pattern can be abstracted away with a custom middleware. Check out the “real world” example in Redux repo to see how it can be done.
All but the aborting could be accomplished while staying immutable by using a request factory which attaches .then and .catch (or equivalent, depending on promise flavor) to the request before returning it.
you can have a action which executes its operation in addition it calls another action, to achieve this you need to have a redux-async-transitions, the example code is given below
function action1() {
return {
type: types.SOMEFUNCTION,
payload: {
data: somedata
},
meta: {
transition: () => ({
func: () => {
return action2;
},
path : '/somePath',
query: {
someKey: 'someQuery'
},
state: {
stateObject: stateData
}
})
}
}
}
and here is for asynchronous call
function asynccall() {
return {
types: [types.PENDINGFUNCTION, types.SUCCESSFUNCTION, types.FAILUREFUNCTION],
payload: {
response: someapi.asyncall() // only return promise
}
meta: {
transition: (state, action) => ({
onPending: () => {
},
onSuccess: (successdata) => {
//gets response data can trigger result based on data
},
onFail: (promiseError) => {
//gets error information used to display messages
}
})
}
}
}
Calling http request in middleware is the same bad ideas as calling it in action creators. You should focus on making our reducers powerful enough to handle asynchronous effects as well as synchronous state transitions. The best way i found while working with redux is describe effects in the reducer(http request is the effect) and then handle it with library like redux-loop or redux-saga
For avoiding code duplication you can extract common code to request function and use it for handling http effects

Resources