brunopk
10/28/2020 - 10:31 PM

Querying multiple HTTP services serially in React

Custom hook to query an arbitrary number of services asynchronously and serially

import {useReducer, useEffect} from 'react';

const A_FETCH_INIT = 'FETCH_INIT';
const A_FETCH_SUCCESS = 'FETCH_SUCCESS';
const A_FETCH_FAILURE = 'FETCH_FAILURE';
const INITIAL_STATE = {
  isLoading: false,
  isError: false,
  result: null,
};

/**
 * Query a list of services asynchronously.
 * @param {Function[]} services list of services (array of length n)
 * @param {Object[]} params params for services (array of length n)
 * @returns {Array} Returns an array with [result, isLoading, isError]
 *
 *  - Initial values: [false, false, []]
 *  - isError is false if one service fails.
 *  - isLoading will return true until all services completes
 *  - result is an array of length n
 */
function useFetchSerial(services, params) {
  const [state, dispatch] = useReducer((prevState, action) => {
    switch (action.type) {
      case A_FETCH_SUCCESS:
        return {
          ...prevState,
          isLoading: false,
          result: action.data,
        };
      case A_FETCH_FAILURE:
        return {
          ...prevState,
          isError: true,
          isLoading: false,
        };
      case A_FETCH_INIT:
        return {
          ...prevState,
          isLoading: true,
          isError: false,
        };
      default:
        return INITIAL_STATE;
    }
  }, INITIAL_STATE);

  if (typeof params === 'undefined') {
    params = services.map((s) => null);
  }

  useEffect(() => {
    const fetchData = async () => {
      try {
        dispatch({type: A_FETCH_INIT});
        let res = [];
        for (let i = 0; i < services.length; i++) {
          let r = await services[i](params[i]);
          res.push(r);
        }
        dispatch({type: A_FETCH_SUCCESS, data: res});
      } catch (error) {
        dispatch({type: A_FETCH_FAILURE});
      }
    };
    if (state.result === null && !state.isLoading && !state.isError) {
      fetchData();
    }
  }, [services, params, state.result, state.isError, state.isLoading]);

  return [state.result, state.isLoading, state.isError];
}

export {useFetchSerial};