Create a queue of Ajax requests with redux-observable and group the results.

Shane Osbourne
InstructorShane Osbourne

Share this video with your friends

Send Tweet
Published 5 years ago
Updated a year ago

With redux-observable, we have the power of RxJS at our disposal - this means tasks that would otherwise be complicated and imperative, become simple and declarative. In this lesson we’ll respond to an action by queuing up 2 separate Ajax requests that will execute sequentially. Then we’ll group the results from both into an array and produce a single action from our epic that will save the data into the redux store

In this application, we have a stories component. The component is connected to the Redux store, and if the state that uses has a loading property, we just return this please wait text. Otherwise, we render out the button you can see on screen here.

Now when this button is clicked, we are calling props.load stories. This is a method that we've bound to the props via this map dispatch function here. So when this function is called, it will dispatch this fetch stories action. This has a type of fetch stories and a default count of five.

Then to handle the successful fetching of the stories, we have fetch stories fulfilled, which accepts some stories and dispatches that in the payload. Both of these actions are handled in the only reducer that we have for this project. Has an initial state which is an empty array for stories and a loading of false.

When it sees the fetch stories action, it will set that stories array back to empty, and it will set loading true. That will allow us to put a please wait sign here. Fetch stories fulfilled will set loading back to false and whatever comes through in the payload as the stories.

So if we go ahead now and click this button, you'll see that we go into a loading state and nothing happens. That's because we are yet to implement the Ajax requests.

Now we're going to retrieve those top five stories from the Hacker News API, which is perfect for an example about Redux observable, because the API requires some composition to get anything useful out of it. So the first thing we need to do is use this top stories URL to get a list of top stories. The trigger for this is the fetch stories action. So we can return actions of type fetch stories.

Now we can do a switch map. Here we have access to the action, so we can destructure the payload from it. Then we'll return observable.ajax.getjson. We'll pass through the top stories URL. Now let's see what that gives us before we go any further.

To do that, we can use the debugging trick of using the do operator and logging out whatever element is coming out of the stream and then just following it up with ignore elements. So if we save this and we need to register the fetch stories epic with our combined epics function here. Then we'll open up the console and see what we get.

It looks like we got an array with 344 items in it, and it is literally just an array of numbers. So that's a great start.

The way this API works is that we can take any of those IDs that we just saw in the console and replace top stories with item/the ID and then .json just as before. So if we want to get the top five stores, we need to first slice the first five items from that array, then convert each of those five items into a full URL like this, and then make those five Ajax requests, group them all together and pass them back into the store.

This type of composition is what RX was built for. So let's look at a way to implement this.

Assuming this returns correctly, we know we've got an array of IDs. So the first thing we need to do is slice the first five IDs. To implement that, we can just call map, and inside here, we have access to the IDs. We can just return IDs.slice zero five. This will take the first five items from the array and produce it as an element in the stream.

The next step was to convert IDs into URLs. We can call map again. This time we're only dealing with those five IDs, so we'll call and pass in the URL function. So for each ID, we'll call this URL function which will receive the ID and replace this part of the URL with that ID.

Next, we said we need to convert URLs to Ajax requests. You probably guessed it, but we can use map again. This time, we'll be dealing with five URLs. So we can call, and then for each URL, we'll return a call to observable get Json. Pass in the URL. Now, at this point of the sequence, if we were to log this out, you'll see that all we have is an array of five observables. They are not going to do anything yet.

So let's head to the browser, look at the console. You can see that the first Ajax request was successful. Then we take the first five items, convert them to URLs, convert them to observables, and that's what you see here. We have an array of five observables just waiting to be subscribed to.

Now we're going to use a higher order observable to achieve this, something that can take an array of observables, call subscribe on each of them, and then group the results back together for us. So we'll call merge map. This will give us access to the elements in the sequence. In our case, it will just be that array of five Ajax requests. It allows us to return another observable.

So if we say that here we have an array of requests and then from this we can call observable.fork join and pass in the requests. Fork join accepts an array of observables and will call subscribe on each of them. It will group the results back together. Crucially for our example, the results will be in the order in which we provided them. So we'll say this step is execute five Ajax requests.

The final step is to take the result of those Ajax requests and dispatch an action back into the Redux store. So we'll say results back into store. For this we'll call map again. This time, we have the results. We can actually call them stories. Then in our actions file, we have this fetch stories fulfilled action. We'll fire that with the stories and import it.

Then we'll try it out in the browser. There you go. We have exactly what we expected. You click this, please wait. It makes the Ajax request for the top stories, converts the first five IDs into five separate Ajax requests, groups the results together, and sends it back into the store.

Our final note here is that I've separated each step in this process out into these individual calls, so we have multiple.maps. Of course, if you wanted to, you could combine these together. For example, you could call map to the URL straightaway there. But I've just kept them on individual lines to make it easier to explain.

Gonzalo Pozzo
Gonzalo Pozzo
~ 5 years ago

I'm trying with this code

import { fetchStories } from "../actions"

const topStories = ``
const story = id =>

const fetchStoriesEpic = action$ =>
    .switchMap(() => Observable.ajax.getJSON(topStories))
    .map(ids => ids.slice(0, 5))
    .map(ids =>
    .map(urls =>
    .mergeMap(reqs => Observable.forkJoin(reqs))

export default fetchStoriesEpic

And getting Uncaught TypeError: Cannot create property 'X-Requested-With' on number '1'

Any idea what it could be?

EDIT: Solved, it looks like when doing .map(urls => a second argument is passed to Observable.ajax.getJSON and breaks the implementation

Param Singh
Param Singh
~ 5 years ago

Why did you use mergeMap during forkJoin in the code?