jmeas/redux-resource

View on GitHub
packages/redux-resource/src/utils/cru-reducer-helper.js

Summary

Maintainability
C
1 day
Test Coverage
import setResourceMeta from './set-resource-meta';
import upsertResources from './upsert-resources';
import requestStatuses from './request-statuses';
import warning from './warning';

// This reducer helper handles the "CRU" in "CRUD".

export default function(state, action, { initialResourceMeta }, updatedMeta) {
  const resources = action.resources;
  const resourcesIsUndefined = typeof resources === 'undefined';
  const hasResources = resources && resources.length;

  let requestKey, requestName;
  if (action.request && typeof action.request === 'string') {
    requestKey = requestName = action.request;
  }
  if (action.requestKey && typeof action.requestKey === 'string') {
    requestKey = action.requestKey;
  }
  if (action.requestName && typeof action.requestName === 'string') {
    requestName = action.requestName;
  }

  let list;
  if (action.list && typeof action.list === 'string') {
    list = action.list;
  }

  if (process.env.NODE_ENV !== 'production') {
    if (!resources) {
      warning(
        `A 'resources' array was not included in a Redux Resource ` +
          `"success" action with type "${action.type}. Without a 'resources' ` +
          `Array, Redux Resource will not be able to track which resources ` +
          `were affected by this CRUD operation. You should check your Action ` +
          `Creators to make sure that they always include a 'resources' array. ` +
          `For more information, refer to the request action documentation at: ` +
          `https://redux-resource.js.org/docs/requests/request-actions.html`,
        'SUCCESS_NO_RESOURCES'
      );
    } else if (!Array.isArray(resources)) {
      warning(
        `A non-array 'resources' value was passed to a Redux Resource ` +
          `"success" action with type "${
            action.type
          }". 'resources' must be an ` +
          `array. If your backend returned a single object, be sure to wrap it ` +
          `inside of an array. If you're using the Redux Resource XHR ` +
          `library, you can do this using the "transformData" option. ` +
          `For more information, refer to the request action documentation at: ` +
          `https://redux-resource.js.org/docs/requests/request-actions.html`,
        'NON_ARRAY_RESOURCES'
      );
    }
  }

  // Without resources, a list, or a request key, there is nothing to update
  if (!hasResources && !requestKey && !list) {
    return state;
  }

  const newResources = upsertResources(
    state.resources,
    resources,
    action.mergeResources
  );
  const newMeta = setResourceMeta({
    resources,
    meta: state.meta,
    newMeta: updatedMeta,
    mergeMeta: action.mergeMeta,
    initialResourceMeta,
  });

  let newRequests;
  if (requestKey) {
    const existingRequest = state.requests[requestKey] || {};
    const newRequest = {
      ...existingRequest,
      ...action.requestProperties,
      resourceType: action.resourceType || action.resourceName,
      requestKey,
      status: requestStatuses.SUCCEEDED,
    };

    if (requestName) {
      newRequest.requestName = requestName;
    }

    let newRequestIds;
    if (hasResources) {
      newRequestIds = resources.map(resource => {
        return typeof resource === 'object' ? resource.id : resource;
      });
    }

    newRequest.ids = newRequestIds || [];

    newRequests = {
      ...state.requests,
      [requestKey]: newRequest,
    };
  } else {
    newRequests = state.requests;
  }

  let newLists;
  if (list) {
    const currentList = state.lists[list] || [];
    let newList;

    if (action.mergeListIds === false) {
      if (hasResources) {
        newList = resources.map(resource => {
          return typeof resource === 'object' ? resource.id : resource;
        });
      } else if (!resourcesIsUndefined) {
        newList = [];
      }
    } else if (hasResources) {
      newList = Array.prototype.slice.call(currentList);

      resources.forEach(resource => {
        const id = typeof resource === 'object' ? resource.id : resource;
        if (!newList.includes(id)) {
          newList.push(id);
        }
      });
    }

    newLists = {
      ...state.lists,
      [list]: newList || currentList,
    };
  } else {
    newLists = state.lists;
  }

  return {
    ...state,
    resources: newResources,
    meta: newMeta,
    requests: newRequests,
    lists: newLists,
  };
}