state.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /**
  2. * state.js
  3. *
  4. * The application uses Redux to handle the state.
  5. * Redux has a **store** which allows access to the one big state of the
  6. * modules. It also has a dispatcher, which receives actions and calls
  7. * reducer functions to act on them.
  8. *
  9. * The store handles all state changes. For that, a module
  10. * defines **action creators**, which are helper functions generating action
  11. * objects. An action object contains a 'type' property, which gives the action
  12. * a name, and a payload, which will be processed.
  13. *
  14. * The processing unit is called a **reducer** and is exported as 'reducer' in
  15. * this file. It is a function which receives
  16. * the current state and an action and returns the processed state. It is good
  17. * practice, that the reducer doesn't change the state (which is passed by
  18. * reference), but creates a modified state and returns it. Also, the reducer
  19. * should return immediately. If an asynchronous operation has to be executed,
  20. * it is handled through redux-sagas, which is basically a concurrent process
  21. * triggered by a reducer, which generates a new action once it's completed.
  22. *
  23. * Action creators (defined as 'actions' in this file) are bound to the
  24. * Redux dispatcher in 'index.js'. When they are called, the dispatcher sends
  25. * the greated action to **all** reducers. This module, and other modules which
  26. * change the state of this module, use these action creators to modifiy the
  27. * state.
  28. *
  29. * This file exports everything which is necessary to set up and operate the
  30. * state of this module.
  31. * - actions
  32. * - reducers
  33. * - state
  34. * - sagas
  35. **/
  36. /* Import NAME and DATA to be able to create action creators */
  37. import { NAME, DATA } from './constants'
  38. import primary from './initialData'
  39. import { delay } from 'redux-saga'
  40. import { call, put, takeEvery } from 'redux-saga/effects'
  41. /** actionTypes define what actions are handeled by the reducer. */
  42. const actionTypes = {}
  43. export const actionCreators = {}
  44. // Generate default actionsTypes CREATE, UPDATE, REMOVE
  45. DATA.forEach(dataItem => {
  46. ['create', 'update', 'delete'].forEach(action => {
  47. // The Redux convention is to name action types e.g. demo_module/UPDATE
  48. // For action creators, we define here the name e.g. removePrimary(id, data)
  49. // where id is the element id and data is the element itself.
  50. const actionType = `${action.toUpperCase()}_${dataItem.toUpperCase()}`
  51. const actionName = `${action}${dataItem[0].toUpperCase()}${dataItem[0].substring(1)}`
  52. actionCreators[actionName] = (id, data) => { return { type: actionType, id, data } }
  53. actionTypes[actionType] = `${NAME}/${actionType}`
  54. })
  55. })
  56. /* in our case we generate e.g.
  57. createPrimary (id, data) {
  58. // id will be ignored, data will be inserted into state.primary
  59. }
  60. updateSecondary1 (id, data) {
  61. // id is used to find the secondary item. data is the new value of the item.
  62. }
  63. removeSecondary2 (id, data) {
  64. // id is used to find the secondary item.
  65. }
  66. */
  67. // Add specific action creators here:
  68. actionCreators['loadSamples'] = () => { return { type: `${NAME}/LOAD_SAMPLES` } }
  69. console.log('State actionTypes, actionCreators', actionTypes, actionCreators)
  70. /** state definition */
  71. /** It is generally easier to not have another object here. If you can combine everything into
  72. * the primary data, do it. */
  73. export const state = []
  74. console.log('State state', state)
  75. /** reducer is called by the redux dispatcher and handles all component actions */
  76. /** The module's root reducer tries to split up the state and let specific
  77. * specialized reducers handle the work. This is not necessary, but more
  78. * convenient and readable. */
  79. // In this example it is assumed, that the secondary items have a key 'primaryId',
  80. // which references the primary data element.
  81. export function reducer (state = [], action) {
  82. console.log(`Entering demo_module root reducer.`, state, action)
  83. const idx = state.findIndex(elem => {return (action.id === elem.id)})
  84. if (action.type.match(/SECONDARY/)) {
  85. // leave immediately, if no idx was found (operations on secondary not valid )
  86. if (idx < 0) {
  87. return state
  88. }
  89. console.log(`Using secondary reducer.`)
  90. const subState = state.secondary
  91. const reducedState = secondaryReducer(subState, action)
  92. const nextState = {
  93. ...state,
  94. secondary: reducedState
  95. }
  96. console.log('Leaving demo_module root reducer', subState, reducedState, nextState)
  97. return nextState
  98. }
  99. switch (action.type) {
  100. case actionTypes.CREATE_PRIMARY:
  101. return state
  102. case actionTypes.UPDATE_PRIMARY:
  103. return state
  104. case actionTypes.REMOVE_PRIMARY:
  105. return state
  106. case actionTypes.LOAD_SAMPLES:
  107. return state
  108. default:
  109. return state
  110. }
  111. }
  112. function secondaryReducer (state = [], action) {
  113. console.log(`Entering secondary reducer.`, state, action)
  114. const idx = state.findIndex(elem => {return (action.id === elem.id)})
  115. let nextState
  116. switch (action.type) {
  117. case actionTypes.CREATE_SECONDARY:
  118. nextState = [ ...state, action.data ]
  119. console.log(`Creating secondary.`, state, nextState)
  120. return nextState
  121. case actionTypes.UPDATE_SECONDARY:
  122. nextState = [ ...state.slice(0, idx), action.data, ...state.slice(idx + 1)]
  123. console.log(`Updating secondary.`, idx, state, nextState)
  124. return nextState
  125. case actionTypes.DELETE_SECONDARY:
  126. nextState = [ ...state.slice(0, idx), ...state.slice(idx + 1) ]
  127. console.log(`Deleting secondary.`, idx, state, nextState)
  128. return nextState
  129. default:
  130. return state
  131. }
  132. }
  133. console.log('State reducer', reducer)
  134. /** sagas are asynchronous workers (JS generators) to handle the state.
  135. // Worker
  136. export function * incrementAsync () {
  137. try {
  138. const data = yield call(Api.isIncrementOk)
  139. yield put({ type: 'INCREMENT_SUCCESS', data })
  140. } catch (error) {
  141. yield put({ type: 'INCREMENT_FAIL', error})
  142. }
  143. }
  144. // Watcher (intercepts INCREMENT_REQUEST, dispatches INCREMENT_SUCCESS or INCREMENT_FAIL in return.)
  145. export function * watchIncrementAsync () {
  146. yield takeEvery('INCREMENT_REQUEST', incrementAsync)
  147. }
  148. // export all sagas in parallel
  149. function * sagas () {
  150. yield takeEvery('INCREMENT_REQUEST')
  151. yield takeEvery('DECREMENT_REQUEST')
  152. } */
  153. export const sagas = null
  154. console.log('State sagas', sagas)