Source: reducers/collectionsReducers.js

  1. /**
  2. * @module react-cmf/lib/reducers/collectionsReducers
  3. */
  4. import { Map, List, fromJS } from 'immutable';
  5. import invariant from 'invariant';
  6. import CONSTANTS from '../constant';
  7. export const defaultState = new Map();
  8. /**
  9. * Get element id. If it doesn't have "id" property, we consider it as immutable.
  10. */
  11. export function getId(element) {
  12. const id = element.id;
  13. if (id === undefined) {
  14. return element.get('id');
  15. }
  16. return id;
  17. }
  18. /*
  19. * backward compatibility, as mutateCollection action creator still use 'id' field
  20. * to represent path to collection
  21. */
  22. export function getActionWithCollectionIdAsArray(action) {
  23. if (action.collectionId || action.id) {
  24. const collectionId = action.collectionId || action.id;
  25. return {
  26. ...action,
  27. collectionId: Array.isArray(collectionId) ? collectionId : collectionId.split('.'),
  28. };
  29. }
  30. return action;
  31. }
  32. /**
  33. * addElementToCollection
  34. *
  35. * @param state current redux state
  36. * @param action redux action
  37. * @returns {object} the new state
  38. */
  39. function addCollectionElement(state, action) {
  40. if (action.operations.add) {
  41. return action.operations.add.reduce((s, e) => {
  42. const element = s.getIn(action.collectionId);
  43. if (List.isList(element)) {
  44. return s.setIn(action.collectionId, element.push(e));
  45. }
  46. if (Map.isMap(element)) {
  47. return s.setIn(action.collectionId, element.merge(e));
  48. }
  49. return state;
  50. }, state);
  51. }
  52. return state;
  53. }
  54. function deleteListElements(state, action) {
  55. function shouldBeRemoved(element) {
  56. return action.operations.delete.indexOf(getId(element)) >= 0;
  57. }
  58. const collection = state.getIn(action.collectionId);
  59. if (collection.some(shouldBeRemoved)) {
  60. return state.setIn(action.collectionId, collection.filterNot(shouldBeRemoved));
  61. }
  62. return state;
  63. }
  64. function deleteMapElements(state, action) {
  65. const collection = state.getIn(action.collectionId);
  66. if (action.operations.delete.some(id => collection.has(id))) {
  67. const changedCollection = action.operations.delete.reduce(
  68. (collectionAccu, element) => collectionAccu.delete(element),
  69. collection,
  70. );
  71. return state.setIn(action.collectionId, changedCollection);
  72. }
  73. return state;
  74. }
  75. /**
  76. * deleteElementFromCollection
  77. *
  78. * @param state current redux state
  79. * @param action redux action
  80. * @returns {object} the new state
  81. */
  82. function deleteCollectionElement(state, action) {
  83. if (action.operations.delete) {
  84. const collection = state.getIn(action.collectionId);
  85. if (Map.isMap(collection)) {
  86. return deleteMapElements(state, action);
  87. } else if (List.isList(collection)) {
  88. return deleteListElements(state, action);
  89. }
  90. throw new Error('CMF collection deletion is only compatible with ImmutableJs List and Map');
  91. }
  92. return state;
  93. }
  94. function updateListElements(state, action) {
  95. const updates = action.operations.update;
  96. const changedCollection = state
  97. .getIn(action.collectionId)
  98. .map(element => updates[getId(element)] || element);
  99. return state.setIn(action.collectionId, changedCollection);
  100. }
  101. function updateMapElements(state, action) {
  102. const updates = action.operations.update;
  103. const changedCollection = Object.keys(updates).reduce(
  104. (collectionAccu, id) => collectionAccu.set(id, updates[id]),
  105. state.getIn(action.collectionId),
  106. );
  107. return state.setIn(action.collectionId, changedCollection);
  108. }
  109. /**
  110. * updateCollectionElement
  111. *
  112. * @param state current redux state
  113. * @param action redux action
  114. * @returns {object} the new state
  115. */
  116. function updateCollectionElement(state, action) {
  117. if (action.operations.update) {
  118. const collection = state.getIn(action.collectionId);
  119. if (Map.isMap(collection)) {
  120. return updateMapElements(state, action);
  121. } else if (List.isList(collection)) {
  122. return updateListElements(state, action);
  123. }
  124. throw new Error('CMF collection update is only compatible with ImmutableJs List and Map');
  125. }
  126. return state;
  127. }
  128. /**
  129. * mutateCollection
  130. *
  131. * @param {object} state the current redux state
  132. * @param {object} action redux action
  133. * @returns {object} the new state
  134. */
  135. function mutateCollection(state, action) {
  136. if (!action.operations || !state.hasIn(action.collectionId) || state.isEmpty()) {
  137. return state;
  138. }
  139. let newState = addCollectionElement(state, action);
  140. newState = deleteCollectionElement(newState, action);
  141. return updateCollectionElement(newState, action);
  142. }
  143. /**
  144. * @param {object} state the state
  145. * @param {object} action redux action
  146. * @return {object} the new state
  147. */
  148. function collectionsReducers(state = defaultState, action = { type: '' }) {
  149. const newAction = getActionWithCollectionIdAsArray(action);
  150. switch (newAction.type) {
  151. case CONSTANTS.COLLECTION_ADD_OR_REPLACE:
  152. return state.setIn(newAction.collectionId, fromJS(newAction.data));
  153. case CONSTANTS.COLLECTION_REMOVE:
  154. if (!state.getIn(newAction.collectionId)) {
  155. invariant(
  156. process.env.NODE_ENV === 'production',
  157. `Can't remove collection ${newAction.collectionId} since it doesn't exist.`,
  158. );
  159. return state;
  160. }
  161. return state.deleteIn(newAction.collectionId);
  162. case CONSTANTS.COLLECTION_MUTATE:
  163. return mutateCollection(state, newAction);
  164. default:
  165. return state;
  166. }
  167. }
  168. export default collectionsReducers;