Reducers - Split and combine
We can now try to implement the "add a code" functionality. We need:
- to add a new action creator;
- to process the corresponding action within the reducer.
The action creator needs to know which code list we're adding a code to:
let lastId = 0
function addCode(codeListId) {
return {
type: 'ADD_CODE',
payload: {
codeListId,
id: `new_code_${lastId++}`
}
}
}
Notice we gave an id to our code. To keep things simple, we created an incremental id, but in the real application we use a randomly generated id.
The reducer logic gets more complex:
function simpleReducer(state, action) {
if (action.type === 'EDIT_CODE_LABEL') {
...
}
else if (action.type === 'ADD_CODE') {
//get the relevant code list
const codeList = state.codeListById[payload.codeListId]
//get the actual codes for the code list
const codes = codeList.codes
//add the code id at the end
const newCodesIds = [...codeIds, payload.id]
//update `codeListById` with the new codes
const codeListById = {
...state.codeListById,
[payload.codeListId]: {
...codeList,//we keep all exiting keys in the code list (here the
//code list label)
codes: newCodesIds //we udpate the codes key
}
}
//we update the codes by adding an entry for the new code
const codeById = {
...state.codeById,
[payload.id]: {
label: ''
}
}
//we return the new state
return {
codeListById,
codeById
}
}
//we return the current state: the action is not supposed to have any effect
//on the state
else return state
}
The code gets quickly very long: we need to process the effects on the different state's entries, without mutating the initial state (which would have made the code shorter). Thanks to the way we shape our state (one entry per kind of entity with references across entities based on ids, as opposed to a nested structure where code lists would contain code descriptions), we can easily split our main reducer into two smaller reducers, working independently one from another:
- a reducer to handle the code lists;
- a reducer to handle the codes.
We then combine these reducers to handle all the application logic.
import { combineReducers } from 'redux'
function codeListByIdReducer(state, action) {
const { type, payload } = action
if (type === 'ADD_CODE') {
const codeList = state[payload.codeListId]
return {
...state,
[payload.codeListId]: {
...codeList,
codes: [...codeList.codes, payload.id]
}
}
}
else return state
}
function codeByIdReducer(state, action) {
const { type, payload } = action
if (type === 'EDIT_CODE_LABEL') {
const code = state[payload.id]
return {
...state,
[payload.id]: {
...code,
label: payload.label
}
}
}
else if (action.type === 'ADD_CODE') {
return {
...state,
[payload.id]: {
label: ''
}
}
}
else return state
}
const mainReducer = combineReducers({
codeListById: codeListByIdReducer,
codeById: codeByIdReducer
})
We can now update our CodeListEditor
component to pass it the addCode
function (the action creator wrapped into a dispatch call through the connect mechanism):
//The `addCode` action creator needs to be passed the id of the code list
//we're adding a code to. Hence, the `addCode` function passed to the
//`CodeListEditorDumb` component (which wraps the action creator with
//a dispatch call) needs this argument too. It is available through the `id`
//prop: when we call `<CodeListEditor id='code_list_1' />`, the `id` prop is
//passed to the `CodeListEditor`, and the connect mechanism will make
//this prop follow up to the `CodeListEditorDumb` component.
function CodeListEditorDumb({ id, codes, editCodeLabel, addCode }) {
return (
<div>
<button onClick={() => addCode(id) }>
Add a code
</button>
<div>
...
</div>
)
}
Play with this pen