Let's say I have an api endpoint
/categories
which returns an array of categories
[
{
id: '1'
name: 'category-1'
},
{
id: '2'
name: 'category-2'
}
...
]
and endpoints for retriving the items within category items/:categoryId
which returns an array of items
[
{ id: '1', name: 'item-1' },
{ id: '2', name: 'item-2' }
...
]
On the UI I display a list of categories, which I can expand and lazy load the list of items. I want to be able to have multiple categories expanded and need to be able to add, edit and delete items.
What is the best way to organize the state for such scenerio?
At the moment my state looks like this:
{
entities: {
categories: {
'1': {
id: '1'
name: 'category-1'
},
'2': {
id: '2'
name: 'category-2'
},
...
},
items: {
'1': {
id: '1'
name: 'item-1'
},
'2': {
id: '2'
name: 'item-2'
},
...
}
},
categories: {
ids: ['1', '2', ...],
isFetching: bool
error: null
},
itemsByCategory: {
'1': {
ids: ['1', '2',...]
isFetching: bool,
error: null
}
...
}
}
In itemsByCategory
the keys are ids of categories, if items for the given category are not loaded yet, the key will not exists on itemsByCategory
.
This solution works, but has some drawbacks. In order to delete item I have to pass two keys (item id and category id) instead of just item id (I could also go through all categories to find item, but it might become slow).
I am also not happy with checking if items for given category were loaded. (first I have to check if the key with category id is defined on
itemsByCategory
), so my selectors become a little bit complicated.
Is there any better way to shape the state for such cases?
If category
is a property of an item
every item should have it's categoryId
property, to store only a reference to it and avoid replicating the categories everywhere. If the items returned by the API call don't include the category you can assign immediately after you have the results from the API, since you know the category because you've used it to retrieve the items in the first place.
Both categories
and itemsByCategory
replicate the information you already have in entites
by storing the ids of the elements, you don't need to store those.
If the only thing you're lazily loading are the items of every category, in categories
create an array of loaded categories to check if you have to make the API call or not.
itemsByCategory
looks completely useless to me, unless you're also lazily loading more information about each item.
I wouldn't worry about looping through all of the items to retrieve those that correspond to each category, as long as you only do it for those categories that are expanded. After all React is meant to be used this way and is really good at it. If you're having performance issues it's probably because you're changing the parent component and forcing a re-render of all the lists.
If all of this fails you just have too much data to render, and you might rethink the UI and add some type of pagination.
Another option is to purge the items from entities
once the category is collapsed, and remove the category from the list of loaded ones, but this might negatively affect your users.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With