greenkeeper[bot] on cross-env-6.0.3
chore(package): update cross-en… (compare)
greenkeeper[bot] on @types
chore(package): update @types/s… (compare)
greenkeeper[bot] on cross-env-6.0.2
chore(package): update cross-en… (compare)
greenkeeper[bot] on cross-env-6.0.1
chore(package): update cross-en… (compare)
tap((action) => console.log('got action', action))@RikuVan When I say read and write observables, I mean observables dedicated to reading data when an action occurs and dispatching that data out as another action.
Write observables will accept data and write to a single source whether that be localStorage, an external API, or back to Redux state. Sometimes I have these combined, but my most-recent projects separate them out since I can make more-generic epics this way.
type, nothing else. I often pass a namespace prop or something to help figure out what's going on when actions are generic. You'll need to figure out a debugging solution yourself. In my Node.js projects, I can't use Redux Devtools, so I create my own logger which logs the action type and the namespace prop if it exists. Those little things greatly help when debugging. I also add a ofNamespace filter similar to ofType so I can mergeMap accordingly. You could also use groupBy so you can safely switchMap multiple different API calls with a single epic.
I'm struggling with writing an epic that on action calls a function (which comes from a node module) that I would like to wrap using bindNodeCallback... I somehow solved how to call it but it looks like I missed something because I cannot fire actions based on the mapping of error and results... My actual code looks like this:
const fnAsObservable = bindNodeCallback(fn);
...
export function myEpic(action$) {
return (
action$.pipe(
ofType(MY_ACTION_TYPE),
mergeMap((action) =>
fnAsObservable().pipe(
map(results => actionDone(results)),
catchError(err => actionFail(err))
)
)
)
);
}The fnAsObservable is correctly called but the MY_ACTION_TYPE is "fired" in endless loop... I also tried to .map directly (instead of pipe() the fnAsObservable, but then I got an error saying the .map was not a function)
Thank you in advance for the help!
@xavier7179 There are a few reasons there would be an infinite loop. You need to look at the output of this. Add a tap after the catchError and log the output. That way, we can track down the issue. The code you wrote looks good otherwise and shouldn't cause infinite loops. You'll want to take a look at fnAsObservable() to see what it's doing, check to make sure actionDone doesn't have the type MY_ACTION_TYPE, and also that after ACTION_DONE is called, something else isn't dispatching MY_ACTION_TYPE.
This issue could be anywhere in your flow. Without any debugging information, it will be impossible to fix.
Any tips on using an async function in a mergeMap operator? Here's what I'm trying to do:
const fetchClientsEpic: Epic<any, any, AppState> = (action$, state$, context) =>
action$.pipe(
filter(isActionOf(fetchClients.request)),
mergeMap(async () => { // This doesn't work!
const token = await context.client!.getTokenSilently();
return ajax
.getJSON<fetchClientsResponse>(
apiEndpoints.clients,
tokenToHeader(token)
)
.pipe(
flatMap((data) => [
// ...
]),
catchError((e) => of(fetchClients.failure(e.xhr.response)))
);
})
);I get the following error: Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
async keyword from the mergeMap function, and instead convert the promise returned by context.client!.getTokenSilently() to an Observable by using from(context.client!.getTokenSilently()) and then use it as an Observable stream that either emits (one event and closes) or emits an error
@liamzdenek thanks, I do think I see what you're getting at. This did the trick:
const fetchClientsEpic: Epic<any, any, AppState> = (action$, state$, context) =>
action$.pipe(
filter(isActionOf(fetchClients.request)),
mergeMap(() =>
from(context.client!.getTokenSilently() as Promise<string>).pipe(
mergeMap((token: string) =>
ajax
.getJSON<fetchClientsResponse>(
apiEndpoints.clients,
tokenToHeader(token)
)
.pipe(
flatMap((data) => [
//...
]),
catchError((e) => of(fetchClients.failure(e.xhr.response)))
)
)
)
)
);Is the nested mergeMap necessary or is there is a cleaner way?
state$ is because context.client.getTokenSilently() has some helper tooling to retrieve the token if the cached token is valid or fetch a new token if the cached token is expired. I'm trying out a new Auth0 library, still not sure if it's better than my previous technique)
You can write rxjs code a few different ways generally, and it's mostly a matter of taste/readability as far as how you'd like to write it.
You could do something like this:
action$.pipe(
filter(isActionOf(fetchClients.request))
withLatestFrom(
from(context.client!.getTokenSilently() as Promise<string>)
),
mergeMap([action, token] => {
return ajax.getJSON(...)
.pipe(
mergeMap(data => ...),
catchError(e => ...)
);
}),
);
withLatestFrom with the most recent action when the BehaviorSubject happens to change