Reputation: 1283
This isn't the first time a question like this has been asked, but for some reason I'm having a real hard time wrapping my head around Observables and RxJs, so I thought I'd ask for some clarification.
I am trying to write an epic which will do the following:
I have read about concatMap
and forkJoin
which seem like they could be useful, as concatMap
should allow me to execute multiple actions consecutively, and forkJoin becuase it can be used for returning the results of the final 3 API calls as a single array.
Here's some code to try and give an idea of what I want. Apologies for the repetitive question, but I'd be grateful for an answer.
export const apiFetchEpic = (
action$: any,
state$: any,
{ apiClient }: IDependencies,
) =>
action$.pipe(
ofType(API_FETCH),
debounce(() => timer(300)),
withLatestFrom(state$),
mergeMap(
([
{
payload: { config, uri },
},
]: [{ payload: IApiFetchActionProps }, any]) => {
return from(apiClient.get(uri, config)).pipe(
mergeMap(res =>
of(
apiSetAction(res), // <-- I need the results of this to be passed into the next calls
apiCompleteAction({ uri }),
),
),
catchError(error =>
of(apiCompleteAction({ uri }), apiErrorAction(error)),
),
)
},
),
)
Upvotes: 1
Views: 2163
Reputation: 1283
I solved it like this:
import { ofType, combineEpics } from 'redux-observable'
import {
mergeMap,
withLatestFrom,
catchError,
debounce,
switchMap,
} from 'rxjs/operators'
import { from, of, timer, forkJoin } from 'rxjs'
import { IDependencies } from '../../configure-store'
import { apiErrorAction } from '../../reducers/errors/actions'
import {
TOC_FETCH,
tocSetAction,
ITocFetchActionProps,
tocCompleteAction,
} from '../../reducers/table-of-contents/actions'
import { defaults } from 'lodash'
// Can probably use forkJoin here, and create multiple API calls as a parallel observable
// https://github.com/patricktran/react-redux-observable-epic-forkjoin-apis/blob/master/src/epics/index.js
export const tocFetchEpic = (
action$: any,
state$: any,
{ apiClient }: IDependencies,
) => {
return action$.pipe(
ofType(TOC_FETCH),
debounce(() => timer(300)), // anti-DDoS
withLatestFrom(state$),
mergeMap(
([
{
payload: { instance },
},
]: [{ payload: ITocFetchActionProps }, any]) => {
const params = { 'myParam': instance, limit: 1 }
return from(apiClient.get('endpoint', { params })).pipe(
mergeMap(
(result): any => {
const def = {
limit: -1,
'newParam': result.data.results[0],
}
const configurations = [
{
uri: 'endpointOne',
config: defaults({}, def),
},
{
uri: 'endpointTwo',
config: defaults(
{
'specialParam': 'table',
'specialParamTwo': 'http://example.com',
'iso2:lang': 'en',
},
def,
),
},
{
uri: 'endpointThree',
config: defaults(
{
'anotherSpecialParam': 'role',
},
def,
),
},
]
// Create a bunch of observables
const parallelObservables = configurations.map(api =>
from(
apiClient.get(api.uri, { params: api.config }),
).pipe(
switchMap(response => of(response)),
catchError(err =>
of(apiErrorAction(err), tocCompleteAction({ instance })),
),
),
)
// Return a forkJoin of them.
return forkJoin(
parallelObservables,
(names: any, schoolNames: any, storeNames: any) => {
//projection
return { // <- The object returned here is ultimately what gets written into state
instance: result.data.results[0],
tables: names.data.results,
labels: [
...schoolNames.data.results,
...storeNames.data.results,
],
}
},
).pipe(
switchMap(response => {
return of(
tocSetAction(response),
tocCompleteAction({ instance }),
)
}),
)
},
),
catchError(error =>
of(tocCompleteAction({ instance }), apiErrorAction(error)),
),
)
},
),
)
}
export default combineEpics(tocFetchEpic)
Upvotes: 1