0
votes

I am new to rxjs and redux-observable. Trying to create an epic that allows me to do multiple parallel ajax calls and dispatches respective actions on success:

const loadPosters = (action$) => action$.pipe(
  ofType(Types.LOAD_FILMS_SUCCESS),
  switchMap(({ films }) =>
    forkJoin(films.map(film =>
      ajax
      .getJSON(`https://api.themoviedb.org/3/search/movie?query=${film.title}`)
      .pipe(
        map(response => {
          const [result] = response.results;
          const poster = `http://image.tmdb.org/t/p/w500/${result.poster_path}`;
          return Creators.savePoster(film, poster);
        })
      ),
    ))
  ),
);

Creators.savePoster() is an action creator for an action named SAVE_POSTER. But, whenever i run my application, no such action is dispatched. Instead i get an error message in browser console:

Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.

edit

Tried a simplified version without forkJoin, sadly yielding the same result:

const loadPosters = (action$) => action$.pipe(
  ofType(Types.INIT_SUCCESS),
  mergeMap(({ films }) =>
    films.map(film =>
      ajax
      .getJSON(`https://api.themoviedb.org/3/search/movie?query=${film.title}`)
      .pipe(
        map(response => {
          const [result] = response.results;
          const poster = `http://image.tmdb.org/t/p/w500/${result.poster_path}`;
          console.log(Creators.savePoster(film, poster));
          return Creators.savePoster(film, poster);
        })
      )
    ),
  ),
);

Appendix

Just for reference, I have another epic which does a simple ajax call which works fine:

const loadFilms = action$ => action$.pipe(
  ofType(Types.INIT_REQUEST),
  mergeMap(() =>
    ajax
    .getJSON('https://star-wars-api.herokuapp.com/films')
    .pipe(
      map(response => Creators.initSuccess(response))
    ),
  ),
);
1

1 Answers

0
votes

The problem is that i don't return an Observable in my inner map. Changing:

return Creators.savePoster(film, poster);

to

return of(Creators.savePoster(film, poster));

makes it work.

By the way, if used with forkJoin it's also possible (and in my case better) to take the mapped results after all requests resolved and dispatch a single action instead of multiple ones:

const loadPosters = (action$) => action$.pipe(
  ofType(Types.INIT_SUCCESS),
  mergeMap(({ films }) =>
    forkJoin(
      films.map(film =>
        ajax
        .getJSON(`https://api.themoviedb.org/3/search/movie?query=${film.title}`)
        .pipe(
          map(response => ({ film, response }))
        )
      ),
    )
  ),
  mergeMap(data => {
    const posters = data.reduce((acc, { film, response}) => ({
      ...acc,
      [film.id]: `http://image.tmdb.org/t/p/w500/${response.results[0].poster_path}`,
    }), {});
    return of(Creators.savePosters(posters));
  })
);

In fact this is my favorite solution so far.