Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to structure state for lazy loading items per category in redux

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?

like image 319
Michał Avatar asked Aug 13 '16 10:08

Michał


1 Answers

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.

like image 56
Marco Scabbiolo Avatar answered Oct 21 '22 04:10

Marco Scabbiolo