I have the following data coming from an API:
const data = {
id: 1,
name: 'myboard',
columns: [
{
id: 1,
name: 'col1',
cards: [
{ id: 1, name: 'card1' },
{ id: 2, name: 'card2' }
]
},
{
id: 2,
name: 'col2',
cards: [
{ id: 3, name: 'card3' },
{ id: 4, name: 'card4' }
]
},
]
}
As you can see, there are essentially 3 nested levels. The top level contains an id
, name
and a list of columns
. Each column
contains an id
, name
and a list of cards
. Each card
has an id
and name
.
I wish to normalize the data for use in Redux as presented here. I am using normalizr to do this as follows:
const card = new schema.Entity('cards');
const column = new schema.Entity('columns', {
cards: [card]
});
const board = new schema.Entity('boards', {
columns: [column]
});
normalize(data, board)
This results in the following:
{
"entities": {
"cards": {
"1": {
"id": 1,
"name": "card1"
},
"2": {
"id": 2,
"name": "card2"
},
"3": {
"id": 3,
"name": "card3"
},
"4": {
"id": 4,
"name": "card4"
}
},
"columns": {
"1": {
"id": 1,
"name": "col1",
"cards": [1, 2]
},
"2": {
"id": 2,
"name": "col2",
"cards": [3, 4]
}
},
"boards": {
"1": {
"id": 1,
"name": "myboard",
"columns": [1, 2]
}
}
},
"result": 1
}
What I can't seem to figure out is how to have each section (ie: cards
, columns
, boards
) split into two sections, namely byId
and allIds
as per the Redux article referenced above.
Essentially this is to make ordering, sorting etc easier in a React application. Im using the latest version of normalizr (3.2.4).
Here is a CodeSandbox with an example of how you can set up the reducers to handle the normalized state.
Essentially, you will end up with something like this for each of your entities:
// lambda or function - whatever your preference is
const cardsById = (state = {}, action) => {
// if, case, handler function - whatever your preference is
if (action.type === 'ADD_DATA') { // or whatever your initial data load type is
return { ...state, ...action.payload.cards }
}
return state
}
const allCards = (state = [], action) => {
if (action.type === 'ADD_DATA') { // or whatever your initial data load type is
return [...state, ...Object.keys(action.payload.cards)]
}
return state
}
const cards = combineReducers({
byId: cardsById,
allIds: allCards
})
and then combine all of those together:
export default combineReducers({
cards,
columns,
boards
})
The action creators for this are as follows:
const addData = ({ entities }) => ({
type: 'ADD_DATA',
payload: entities // note the rename - this is not required, just my preference
})
// I used a thunk, but theory is the the same for your async middleware of choice
export const getData = () => dispatch => dispatch(addData(normalize(data, board)))
Hope this helps. Remember that you will need to maintain both the byId
and allIds
for each entity as entities are added or removed.