Source: app/util/asyncReducer.js

import handleActions from 'redux-actions/lib/handleActions';
import debugLib from 'debug';

const debug = debugLib('SlimmingWorld:asyncReducer');

export const resolved = 'resolved';
export const rejected = 'rejected';
export const pending = 'pending';

/** @module */

/**
 * Creates an async action handler with a signature as used in redux-actions:
 * (state, action) => newState
 * It will respond to different async states of the action, based on the passed
 * configuration object.
 * @function createAsyncActionHandler
 * @param {Object} asyncConfig The async configuration object
 * @param {function} [asyncConfig.pending] The action handler to be called when
 * the action is a pending (not resolved or rejected) action
 * @param {function} [asyncConfig.rejected] The action handler to be called when
 * the action is a rejected action
 * @param {function} [asyncConfig.resolved] The action handler to be called when
 * the action is a resolved action
 * @returns {function} The action handler
 */
export const createAsyncActionHandler = asyncConfig => (state, action) => {
  if (action.meta.isAsync) {
    if (action.error) {
      return asyncConfig.rejected ? asyncConfig.rejected(state, action) : state;
    } else if (action.meta.isFulfilled) {
      return asyncConfig.resolved ? asyncConfig.resolved(state, action) : state;
    }
    return asyncConfig.pending ? asyncConfig.pending(state, action) : state;
  }

  debug('Async action handler ignored action with no isAsync property on meta');
  return state;
};

/**
 * Enhanced version of the
 * {@link https://github.com/acdlite/redux-actions#handleactionsreducermap-defaultstate|redux-actions handleActions}
 * utility. It has the same API as the handleActions function, but allows for async action handlers
 * in the reducerMap parameter. Instead of passing a callback function for a specific action type,
 * an object can be passed with callback functions for each stage of an asynchronous action.
 * For more info, see
 * {@link module:app/util/asyncReducer~createAsyncActionHandler|createAsyncActionHandler}
 * or the example below.
 *
 * @function handleActionsAsync
 * @param {object} reducerMap The reducer map config
 * @param defaultState The default state for the reducer. See the
 * {@link https://github.com/acdlite/redux-actions#handleactionsreducermap-defaultstate|redux-actions handleActions}
 * docs for more info.
 * @returns {function} The resulting reducer function
 * @example handleActionsAsync({
 *   // regular handleActions syntax is still supported
 *   INCREMENT: (state, action) => ({
 *     counter: state.counter + action.payload
 *   }),
 *   // asynchronous action
 *   GET_ARTICLES: {
 *     pending: (state) => ({ ...state, articlesLoading: true }),
 *     resolved: (state) => ({
 *       ...state,
 *       articlesLoading: false,
 *       articles: action.payload.articles,
 *     }),
 *   },
 * }, { counter: 0, articlesLoading: false, articles: []});
 */
export const handleActionsAsync = (reducerMap, defaultState) => {
  const patchedReducerMap = { ...reducerMap };

  Object.keys(patchedReducerMap).forEach(actionType => {
    if (typeof patchedReducerMap[actionType] === 'object') {
      patchedReducerMap[actionType] = createAsyncActionHandler(patchedReducerMap[actionType]);
    }
  });

  return handleActions(patchedReducerMap, defaultState);
};