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

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

// action

export const FETCH_STORIES = 'FETCH_STORIES';
export const FETCH_STORIES_FULFILLED = 'FETCH_STORIES_FULFILLED';

export function fetchStoriesAction(count = 5) {
  return {
    type: FETCH_STORIES,
    payload: count
  }
}

export function fetchStoriesFulfilledAction(stories) {
  return {
    type: FETCH_STORIES_FULFILLED,
    payload: stories
  }
}
// epics

import {Observable} from 'rxjs';
import {combineEpics} from 'redux-observable';
import {FETCH_STORIES, fetchStoriesFulfilledAction} from "../actions/index";

const topStories = `https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty`;
const url = (id) => `https://hacker-news.firebaseio.com/v0/item/${id}.json?print=pretty`;

function fetchStoriesEpic(action$) {
  return action$.ofType(FETCH_STORIES)
    .switchMap(({payload}) => {
      return Observable.ajax.getJSON(topStories)
        // slice first 5 ids
        .map(ids => ids.slice(0, 5))
        // convert ids -> urls
        .map(ids => ids.map(url))
        // convert urls -> ajax
        .map(urls => urls.map(url => Observable.ajax.getJSON(url)))
        // execute 5 ajax requests
        .mergeMap(reqs => Observable.forkJoin(reqs))
        // results -> store
        .map(stories => fetchStoriesFulfilledAction(stories))
    })
}

export const rootEpic = combineEpics(fetchStoriesEpic);
import {FETCH_STORIES, FETCH_STORIES_FULFILLED} from "../actions/index";

const initialState = {
  stories: [],
  loading: false,
};

export function storiesReducer(state = initialState, action) {
  switch(action.type) {
    case FETCH_STORIES:
      return {
        stories: [],
        loading: true
      };
    case FETCH_STORIES_FULFILLED:
      return {
        stories: action.payload,
        loading: false
      };
    default: return state;
  }
}

export default storiesReducer;
// component

import React from 'react';
import {connect} from "react-redux";
import {fetchStoriesAction} from "../actions/index";

export function Stories(props) {
  if (props.loading) {
    return (
      <p>Please wait...</p>
    )
  }
  return (
    <div>
      <button type="button" onClick={props.loadStories}>Load top 5 stories</button>
      <StoryList stories={props.stories} />
    </div>
  )
}

function StoryList(props) {
  return (
    <ul>
      {props.stories.map(story =>
        <li key={story.id}>
          <a href={story.url}>{story.title}</a>
        </li>
      )}
    </ul>
  );
}

function mapState(state) {
  return state;
}

function mapDispatch(dispatch) {
  return {
    loadStories: () => dispatch(fetchStoriesAction())
  }
}

export default connect(mapState, mapDispatch)(Stories);