So I've just started learning redux and in the process read their documentation on 'Normalizing State Shape'.
One of the key takeaway points is:
"Any references to individual items should be done by storing the item's ID."
I've set up my state as they have advised. So in the example below each taskGroup holds a selection of tasks referenced by their ids and each task holds a selection of comments also referenced by their ids.
{
"taskGroups": {
"byId": {
"taskGroup1": {
"taskGroupId": "taskGroup1",
"tasks": ["task1", "task2"]
//etc etc
},
"taskGroup2": {
"taskGroupId": "taskGroup2",
"tasks": ["task2", "task3"]
//etc etc
}
//etc etc
},
"allIds": ["taskGroup1", "taskGroup2", "taskGroup3"]
},
"tasks": {
"byId": {
"task1": {
"taskId": "task1",
"description": "......",
"comments": ["comment1", "comment2"]
//etc etc
},
"task2": {
"taskId": "task2",
"description": "......",
"comments": ["comment3", "comment4", "comment5"]
//etc etc
}
//etc etc
},
"allIds": ["task1", "task2", "task3", "task4"]
},
"comments": {
"byId": {
"comment1": {
"id": "comment1",
"comment": "....."
//etc etc
},
"comment2": {
"id": "comment2",
"comment": "....."
//etc etc
}
//etc etc
},
"allIds": ["comment1", "comment2", "comment3", "comment4", "comment5"]
}
}
I understand the theory and see the benefits of having my state structured like this. In practice though, I'm struggling to map over an array of references in an object and a bit lost of where I should be doing it.
What and where is the most efficient way to map over the references to the item's Ids with the actual items?
Should I be doing this on the parent component mapping over all the references before passing them down as props to child components? Or should I pass the references down to child components as props and then map over them there?
Before switching to redux I was using useContext but with a normalised state. I used the following function to filter what tasks were needed for each taskGroup. Thanks to ssube who posted this
export const filterObject = (objToFilter: any, valuesToFind: string) => {
return Object.keys(objToFilter)
.filter((key) => valuesToFind.includes(key))
.reduce((obj, key) => {
return {
...obj,
[key]: objToFilter[key],
};
}, {});
};
Which was then used like so (the same logic was then repeated in my Tasks component to map out comments)
{
Object.values(taskGroupsById)
.sort((a, b) => a.sortOrder - b.sortOrder)
.map((taskGroup) => {
return (
<TaskGroup
key={taskGroup.taskGroupId}
taskGroupTitle={taskGroup.taskGroupTitle}
tasks={filterObject(
tasksById,
taskGroupsById[`${taskGroup.taskGroupId}`].tasks
)}
handleDrawer={handleDrawer}
findTaskStatus={findTaskStatus}
findAssignedToTask={findAssignedToTask}
/>
);
});
}
This works ok but I'm not sure if its counter intuitive, as it's beeing calculated in multiple instances of the TaskGroup component instead of just once.
Is there a better method to achieve this? I've tried to create a selector in my slices to recreate this but can't seem to work out how to map over multiple references in an array (as opposed to just one reference as a string).
Any help would be much appreciated even if it is just a nudge in the right direction. Thanks!