reduxjs / redux

A JS library for predictable global state management
https://redux.js.org
MIT License
60.88k stars 15.27k forks source link

Remove many-level nested entities #736

Closed omeshechkov closed 9 years ago

omeshechkov commented 9 years ago

Hi,

I have the next store normalized structure:

{
  sports: {
    1: {
       id: 1,
       name: 'Soccer',
       leagues: [2,3,4]
     },
     ...
  },
  leagues: {
    2: {
       sportId: 1,
       id: 2,
       name: 'Some league',
       matches: [5,6,7]
     },
     ...
  },
  matches: {
    5: {
       leagueId: 1,
       id: 5,
       name: 'Some match'
     },
    ...
  }
}

I have the next reducers composition:

rootReducer(state, action) {
  return {
    sports: sportsReducer(state.sports, action),
    leagues: leaguesReducer(state.leagues, action),
    matches: matchesReducer(state.matches, action)
  }
}

For example I need to remove sport and all dependent entities (leagues and matches). I can remove leagues because a league entity has a sport's id, but match doesn't. So in this way I have to write a single reducer because sportsReducer actually have to change all three maps (sports, leagues and matches).

Is there a more elegant way to deal with such situations?

gaearon commented 9 years ago

Why do you need to remove cached data? Keep all the state fields, but add a new field called visibleSportIds: [1, 2, 3]. Instead of deleting a sport from sports, just remove its ID from this array. Make sure the app only displays sports that exist in visibleSportIds.

omeshechkov commented 9 years ago

Because this is a cut of model what I have, the full mode is much bigger than it. Keeping all data in the store (without removing) will lead to the memory leaks and probably performance problems too, because there will be too much data soon. But the main requirement of our app is performance, so I can't afford it.

Actually, I've already found the solution. I'm going to expand low level entities with "high-level" keys. Hence, I'll be able to remove matches, scores, etc.

Something like this:

scores: {
   sportId: 1,
   leagueId: 2,
   matchId: 3,
   id: 4,
   ...
}

scoreReducer(state, action) {
  switch(action.type) {
    case REMOVE_SPORT:
      //get score IDs by leagueId
      //remove from the state IDs I got
      ...
}

It seems the most adequate solution in my case. The thing I most don't like in this case is iterating over map, but I think I'll reconcile with it.

Anyway thanks for your reply, I'll keep in mind your solution.

gaearon commented 9 years ago

Keeping all data in the store (without removing) will lead to the memory leaks and probably performance problems too, because there will be too much data soon. But the main requirement of our app is performance, so I can't afford it.

Please profile it first (you can generate thousands of items to make sure). You might think keeping data in memory will be an issue, but if you use something like ImmutableJS to make sure mutations aren't that expensive, it may not be.

omeshechkov commented 9 years ago

I have a batch of diffs (actions) every ~1 second, so it's likely will be a problem. The Immutable JS as I understood is only for elegant dealing with collections and there are native objects and arrays under the hood. So there is no difference what I'm using Immutable JS or native objects regarding performance.

But I'll keep your solution in mind, thanks.

gaearon commented 9 years ago

So there is no difference what I'm using Immutable JS or native objects regarding performance.

There is: Immutable uses structural sharing. This means any time you're putting something in the entity cache or changing it, you're not creating giant object under the hood.

zatziky commented 8 years ago

I stick with @omeshechkov . What if he needed to delete leagues from a sportReducer because they were leagues were modified and their modification was persisted to a db. Then you want to start with fresh leagues aka [].