Home Reference Source Test Repository

app/modules/users/reducers/users.reducer.js

'use strict';

/**
 * @typedef {Object} userObj
 * @property {number} id
 * unique identifier
 * @property {string} login
 * user login
 * @property {string} url
 * user profile URL
 * @property {string} avatar_url
 * user profile picture URL
 */

// Libs

import Rx from 'rxjs';

const Observable  = Rx.Observable;
const { ajax }    = Observable;

// Constants

/**
 * constant
 * @type {string}
 */
export const FETCH_USERS          = 'FETCH_USERS';
/**
 * constant
 * @type {string}
 */
export const FETCH_USERS_SUCCESS  = 'FETCH_USERS_SUCCESS';
/**
 * constant
 * @type {string}
 */
export const FETCH_USERS_ERROR    = 'FETCH_USERS_ERROR';

/**
 * list of request states
 * @type {Object}
 * @property {string} PENDING
 * user query is pending response
 * @property {string} FULFILLED
 * user query is fulfilled
 * @property {string} REJECTED
 * user query is rejected
 */
export const requestState = {
  PENDING     : 'PENDING',
  FULFILLED   : 'FULFILLED',
  REJECTED    : 'REJECTED'
};

// Actions

/**
 * action to fetch user
 * @param {?number} [since=RANDOM]
 * id from where to get users, default is a random number
 * @param {?number} [perPage=10]
 * number of user to get, default is 10
 * @return {Object}
 * @property {string} type
 * action type
 * @property {number} since
 * id from where to get users
 * @property {number} perPage
 * number of user to get
 */
export const fetchUsers = (
  since   = Math.floor(Math.random()*500),
  perPage = 10
) => ({ type : FETCH_USERS, since, perPage });

/**
 * action to provide users data to the state
 * @param {Array<userObj>} data
 * user list from API
 * @return {Object}
 * @property {type}
 * action type
 * @property {Array<userObj>} data
 * user list from API
 */
export const fetchUsersSuccess = data => ({ type : FETCH_USERS_SUCCESS, data });

/**
 * action to provide users error logs to state
 * @param error
 * error from failed API request
 * @return {Object}
 * @property {type}
 * action type
 * @property {Object} error
 * error from failed API request
 */
export const fetchUsersError = error => ({ type : FETCH_USERS_ERROR, error });

// Epic

/**
 * Epic observer that query user API when triggered
 * @param {Object} action$
 * action object that trigger the user API request
 * @param {string} action$.type
 * action type
 * @return {(Array<userObj>|Object)}
 */
export const fetchUsersEpic = action$ =>
  action$.ofType(FETCH_USERS)
    .mergeMap(action =>
      ajax(`https://api.github.com/users?per_page=${action.perPage}&since=${action.since}`)
        .map(xhr => fetchUsersSuccess(xhr.response))
        .catch(error => Observable.of(fetchUsersError(error)))
    );

// Reducer

/**
 * reducer for users actions
 * @param {Object} state
 * current state value
 * @param {string} state.requestState
 * user request state
 * @param {Array<userObj>} state.data
 * data gotten from user request
 * @param {?Object} state.error
 * error object from failed user request
 * @param {string} state.error.message
 * error object if an error occurred
 * @param {Object} action
 * action to perform on the state
 * @param {string} action.type
 * describe the action type
 * @return {Object}
 * @property {string} state.requestState
 * user request state
 * @property {Array<userObj>} state.data
 * data gotten from user request
 * @property {?Object} state.error
 * error object from failed user request
 */
const usersReducer = (
  state = {
    requestState : requestState.FULFILLED,
    data         : [],
    error        : null
  },
  action
) => {
  switch ( action.type ) {
    case FETCH_USERS:
      return Object.assign({}, state, {
        requestState : requestState.PENDING,
        data         : [],
        error        : null
      });
    case FETCH_USERS_SUCCESS:
      return Object.assign({}, state, {
        requestState : requestState.FULFILLED,
        data         : action.data,
        error        : null
      });
    case FETCH_USERS_ERROR:
      return Object.assign({}, state, {
        requestState : requestState.REJECTED,
        data         : [],
        error        : action.error
      });
    default:
      return state;
  }
};

export default usersReducer;