reduxjs / redux

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

Single store, single reducer question #655

Closed hekike closed 9 years ago

hekike commented 9 years ago

Hi,

With single reducer: In my current redux application I've one single state with Immutable.js and I've separate reducers which I "merge/reduce" into one big reducer with reduce-reducers. Basically one state and one reducer. I do this because in this way I can change my state's any part from any reducer which is very comfortable for me but I'm curious what's your opinion about this.

With combine reducers: With separate reducers and the combination of them: Wouldn't be hard because of the concurrency issues and reducer duplications to handle when one action needs to change multiple objects in the state? For example: ADD_TO_CART action have to touch cart and products as well.

Thanks!

alkhe commented 9 years ago

It's a matter of preference, really. I don't usually feel comfortable making my state one big, flat Object, for multiple reasons.

But with that comes the scaling tradeoff you mentioned, which is more of an inherent issue in the design style that is encouraged by combineReducers.

gaearon commented 9 years ago

Wouldn't be hard because of the concurrency issues

There is no concurrency in JS.

reducer duplications to handle when one action needs to change multiple objects in the state

Often (not always) when you need to change a lot of objects in response to one action, it's a bad sign. Is your state shape normalized? See “nested entities” note on this page:

In this example, we store the received items together with the pagination information. However, this approach won’t work well if you have nested entities referencing each other, or if you let the user edit items. Imagine the user wants to edit a fetched post, but this post is duplicated in several places in the state tree. This would be really painful to implement.

If you have nested entities, or if you let users edit received entities, you should keep them separately in the state as if it was a database. In pagination information, you would only refer to them by their IDs. This lets you always keep them up to date. The real world example shows this approach, together with normalizr to normalize the nested API responses. With this approach, your state might look like this:

{
  selectedReddit: 'frontend',
  entities: {
    users: {
      2: {
        id: 2,
        name: 'Andrew'
      }
    },
    posts: {
      42: {
        id: 42,
        title: 'Confusion about Flux and Relay',
        author: 2
      },
      100: {
        id: 100,
        title: 'Creating a Simple Application Using React JS and Flux Architecture',
        author: 2
      }
    }
  },
  postsByReddit: {
    frontend: {
      isFetching: true,
      didInvalidate: false,
      items: []
    },
    reactjs: {
      isFetching: false,
      didInvalidate: false,
      lastUpdated: 1439478405547,
      items: [42, 100]
    }
  }
}
gaearon commented 9 years ago

In other words:

  1. If you want to change the same data that is scattered across different state branches in response to an action, and combineReducers makes it harder to do, it's a sign you should not have scattered this data in the first place: keep it normalized!
  2. If you want to change different data in response to a single action, combineReducers is your friend: it helps you decouple these changes, so different teams can work on different mutations in response to the same action inside different reducers, and not worry about merge conflicts.
hekike commented 9 years ago

@gaearon @edge thank you!