Search code examples
reactjsreduxrxjsredux-observableredux-toolkit

How to wait for a different epic to finish and the store to be updated, before starting another epic with redux-observables?


In my scenario, when the app loads I dispatch an action that starts an epic to create an API instance, which is necessary to make other API calls:

const connectApiEpic: Epic = (action$, state$) =>
  action$.pipe(
    ofType('CONNECT_API'),
    mergeMap(async (action) => {
      const sdk = await AppApi.connect({ url: apiUrl });

      return { type: 'SET_API', payload: api };
    })
  );

The UI is already loaded and the API connection happens in the background. Now if the user clicks a certain search button, I have to dispatch another action (epic) to perform a search, which requires the API to be already connected.

Basically, I need to do something like:

const searchItem: Epic = (action$, rootState$) =>
  action$.pipe(
    ofType('SEARCH_ITEM'),
    mergeMap(async (action) => {
      const { api } = rootState$.value;

      const item = await api.search(action.item);

      return { type: 'SET_ITEM', payload: item };
    })
  );

However, this will not work until the API is set in the store from connectApiEpic.

Using redux-observables and rxjs, how could it be made possible to:

  1. connect the api when the app starts, in the background (already soloved)
  2. if the user clicks "search", dispatch an epic to search, but wait for the api to be connected first, or if the api is already connected, then go ahead and perform the search

Solution

  • So, if api is what you need to wait for in searchItem epic, I think this would be an approach:

    const searchItem: Epic = (action$, rootState$) =>
      action$.pipe(
        ofType('SEARCH_ITEM'),
    
        mergeMap(
          // waiting for the api to connect first
          // if it's already there, everything will happen immediately
          action => rootState$.pipe(
            map(state => state.api)
            filter(api => !!api),
          ),
        ),
    
        mergeMap(
          api => from(api.search(action.item)).pipe(
            map(item => ({ type: 'SET_ITEM', payload: item }))
          )
        )
      );