/** @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)