Source: demo_module/state.js

/** @module */

/**
 * state.js
 *
 * The application uses Redux to handle the state.
 * Redux has a **store** which allows access to the one big state of the
 * modules. It also has a dispatcher, which receives actions and calls
 * reducer functions to act on them.
 *
 * The store handles all state changes. For that, a module
 * defines **action creators**, which are helper functions generating action
 * objects. An action object contains a 'type' property, which gives the action
 * a name, and a payload, which will be processed.
 *
 * The processing unit is called a **reducer** and is exported as 'reducer' in
 * this file. It is a function which receives
 * the current state and an action and returns the processed state. It is good
 * practice, that the reducer doesn't change the state (which is passed by
 * reference), but creates a modified state and returns it. Also, the reducer
 * should return immediately. If an asynchronous operation has to be executed,
 * it is handled through redux-sagas, which is basically a concurrent process
 * triggered by a reducer, which generates a new action once it's completed. 
 *
 * Action creators (defined as 'actions' in this file) are bound to the 
 * Redux dispatcher in 'index.js'. When they are called, the dispatcher sends
 * the greated action to **all** reducers. This module, and other modules which
 * change the state of this module, use these action creators to modifiy the 
 * state.
 *
 * This file exports everything which is necessary to set up and operate the 
 * state of this module.
 * - actions
 * - reducers
 * - state
 * - sagas
 **/


// Import NAME and DATA to be able to create action creators */
import { NAME, DATA } from './constants'
import { primary } from './initialData'
import { delay } from 'redux-saga'
import { call, put, takeEvery } from 'redux-saga/effects'


/** state definition */
/** It is generally easier to not have another object here. */
export const state = []
console.log('State state', state)

/** actionTypes define what actions are handeled by the reducer. */
const actionTypes = {}
export const actionCreators = {}

// Generate default actionsTypes CREATE, UPDATE, REMOVE
DATA.forEach(dataItem => {
  ['create', 'update', 'remove'].forEach(action => {
    // The Redux convention is to name action types e.g. demo_module/UPDATE
    // For action creators, we define here the name e.g. removePrimary(id, data)
    // where id is the element id and data is the element itself.
    const actionType = `${action.toUpperCase()}_${dataItem.toUpperCase()}`
    const actionName = `${action}${dataItem[0].toUpperCase()}${dataItem.substring(1)}`
    actionCreators[actionName] = (id, data) => { return { type: `${NAME}/${actionType}`, id, data } }
    actionTypes[actionType] = `${NAME}/${actionType}`
  })
})

// Add specific action creators here:
actionCreators['loadSamples'] = () => { return { type: `${NAME}/LOAD_SAMPLES` } }
actionTypes['LOAD_SAMPLES'] = `${NAME}/LOAD_SAMPLES`
console.log('State actionTypes, actionCreators', actionTypes, actionCreators)



/** reducer is called by the redux dispatcher and handles all component actions */
/** The module's root reducer tries to split up the state and let specific
  * specialized reducers handle the work. This is not necessary, but more
  * convenient and readable. */
  // In this example it is assumed, that the secondary items have a key 'primaryId',
  // which references the primary data element.
export function reducer (state = [], action) {
  // find the primary data element with the matching id.
  const idx = state.findIndex(elem => {return (action.id === elem.id)})
  console.log(`Entering demo_module root reducer.`, state, action, idx)
  let nextState
  if (action.type.match(/SECONDARY/)) {
    // leave immediately, if no idx was found (operations on secondary not valid if there's no primary)
    if (idx < 0) {
      return state
    }
    console.log(`Using secondary reducer.`)
    const subState = state.secondary
    const reducedState = secondaryReducer(subState, action)
    nextState = {
      ...state,
      secondary: reducedState
    }
    console.log('Leaving demo_module root reducer', subState, reducedState, nextState)
    return nextState
  }
  switch (action.type) {
    case actionTypes.CREATE_PRIMARY:
      nextState = [ ...state, action.data ]
      console.log('Creating primary', state, nextState)
      return nextState
    case actionTypes.UPDATE_PRIMARY:
      console.log(idx, state[idx], action.data, {...state[idx], ...action.data})
      nextState = [ ...state.slice(0, idx), {...state[idx], ...action.data}, ...state.slice(idx + 1) ]
      console.log('Updating primary', state, nextState)
      return nextState
    case actionTypes.REMOVE_PRIMARY:
      console.log('wtf')
      nextState = [ ...state.slice(0, idx), ...state.slice(idx+1) ]
      console.log('Removing primary', state, nextState)
      return nextState
    case actionTypes.LOAD_SAMPLES:
      nextState = primary
      console.log('Loading sample data', state, nextState)
      return nextState
    default:
      return state
  }
}

function secondaryReducer (state = [], action) {
  console.log(`Entering secondary reducer.`, state, action)
  const idx = state.findIndex(elem => {return (action.id === elem.id)})
  let nextState
  switch (action.type) {
    case actionTypes.CREATE_SECONDARY:
      nextState = [ ...state, action.data ]
      console.log(`Creating secondary.`, state, nextState)
      return nextState
    case actionTypes.UPDATE_SECONDARY:
      nextState = [ ...state.slice(0, idx), action.data, ...state.slice(idx + 1)]
      console.log(`Updating secondary.`, idx, state, nextState)
      return nextState
    case actionTypes.REMOVE_SECONDARY:
      nextState = [ ...state.slice(0, idx), ...state.slice(idx + 1) ]
      console.log(`Removing secondary.`, idx, state, nextState)
      return nextState
    default:
      return state
  }
}

console.log('State reducer', reducer)

/** sagas are asynchronous workers (JS generators) to handle the state.
// Worker
export function * incrementAsync () {
  try {
    const data = yield call(Api.isIncrementOk)
    yield put({ type: 'INCREMENT_SUCCESS', data })
  } catch (error) {
    yield put({ type: 'INCREMENT_FAIL', error})
  }
}

// Watcher (intercepts INCREMENT_REQUEST, dispatches INCREMENT_SUCCESS or INCREMENT_FAIL in return.)
export function * watchIncrementAsync () {
  yield takeEvery('INCREMENT_REQUEST', incrementAsync)
}

// export all sagas in parallel
function * sagas () {
  yield takeEvery('INCREMENT_REQUEST')
  yield takeEvery('DECREMENT_REQUEST')
} */
export const sagas = null
console.log('State sagas', sagas)