Source: app/reducers/entitiesReducer/entityTypeReducer.js

import mapValues from 'lodash/mapValues';
import entityReducer from './entityReducer';
import {
  REMOVE_ENTITIES,
  ADD_ENTITIES_OF_TYPE,
  UPDATE_ENTITIES,
  SET_ENTITY,
  REMOVE_ENTIRE_ENTITY,
} from '../../actions/entities/entityActions';

/**
 * Reducer for a list of entities of a single entity type. The state is structured
 * as a map with ids as key and calls the entityReducer for the value.
 *
 * {
 *   [entity id]: entityReducer(),
 *   ...
 * }
 */
const entityTypeReducer = (state = {}, action, strictIdCheck = false) => {
  if (action.meta && typeof action.meta.entityId !== 'undefined') {
    if (strictIdCheck && action.type === SET_ENTITY) {
      const dataId = action?.payload.data.id;
      if (dataId && dataId !== action.meta.entityId) {
        throw new Error(
          `Strict id check failed: key "${action.meta.entityId}" of entity does not match id "${dataId}"`,
        );
      }
    }

    return {
      ...state,
      [action.meta.entityId]: entityReducer(state[action.meta.entityId], action, strictIdCheck),
    };
  }

  switch (action.type) {
    case ADD_ENTITIES_OF_TYPE:
      if (strictIdCheck) {
        Object.keys(action.payload).forEach(id => {
          if (action.payload[id].id !== id) {
            throw new Error(
              `Strict id check failed: key "${id}" of entity does not match id "${action.payload[id].id}"`,
            );
          }
        });
      }
      return {
        ...state,
        ...action.payload,
      };
    case REMOVE_ENTITIES:
      return Object.keys(state)
        .filter(key => !action.payload.includes(key))
        .reduce((newState, key) => {
          newState[key] = state[key]; // eslint-disable-line no-param-reassign
          return newState;
        }, {});
    case REMOVE_ENTIRE_ENTITY:
      return Object.keys(state)
        .filter(key => !action.payload.includes(key))
        .reduce((newState, key) => {
          newState[key] = state[key]; // eslint-disable-line no-param-reassign
          return null;
        }, {});
    case UPDATE_ENTITIES:
      return mapValues(state, entityState => {
        const { predicate } = action.meta;
        if (
          !predicate ||
          Object.keys(predicate).every(key => entityState[key] === predicate[key])
        ) {
          return entityReducer(entityState, action);
        }
        return entityState;
      });
    default:
      return state;
  }
};

export default entityTypeReducer;