Paweł Baca
Paweł Baca

Reputation: 888

Redux reducer reference

What happens to the state from redux when we trigger an action?

  1. We have the current state of redux
  2. We call the action
  3. Each reducer looks for its own case in the switch that contains the correct actionType
  4. Each reducer returns the state - does each reducer return a new reference to the state even in case of default return state?

What actually happens to the state if it is returned in reducer? Dose it use some kind of reduce function to compare state and call rerender, then return new reference and react then compare is there any change and invoke rerender?

Upvotes: 1

Views: 511

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42218

What you are talking about here is a reducer which is made by combining multiple "slice" reducers through combineReducers. Something like this:

const rootReducer = combineReducers({
  users: usersReducer,
  posts: postsReducer,
})

Let's say that we dispatch an action with type ADD_USER -- this is something that usersReducer will respond to but the postsReducer won't.

Each reducer returns the state - does each reducer return a new reference to the state even in case of default return state?

The postsReducer doesn't care about this action and will just return state. This is a reference to the existing object. So state.posts is identical.

The usersReducer will create and return a new object for its state. So state.users is a reference to a different object in memory than it was before. If there are other arrays or objects nested inside of state.user then those objects will be the same references unless we replaced them in the reducer. So you are dealing with a "shallow copy" where state.users is a new object, but a deeply nested object like state.users.byId[5] might be the same exact object as before.


You might think that the root state would always be a new object reference when dealing with combineReducers since it is essentially doing this:

const rootReducer = (state, action) => ({
  users: usersReducer(state, action),
  posts: postsReducer(state, action),
});

But the actual implementation is more complex and it involves some checks to prevent unnecessary object creation. If every individual reducer just uses default: return state; then the original state object is returned.

Here's how the combineReducers function works:

  • It has a boolean flag property hasChanged which is initially false.
  • It creates a nextState which is initially an empty object (so obviously that's a new object).
  • It goes through each of the individual property reducers and updates the nextState with the updated state for that property. If the property reducer returned a new state then hasChanged becomes true.
  • If hasChanged is still false after all the property reducers have been called, then it ignores the nextState and instead returns the original state variable.
  • Otherwise it returns the nextState.

When using the React bindings like useSelector, it (by default) does a strict equality check on the returned value. So if you are returning a new object which is an exact copy of the previous object, it will trigger a re-render because the object is a different reference. This is why it is important to use something like reselect to memoize your selectors if they involve creating an object through array.map, array.filter, etc. Otherwise you will have a new object reference every single time. useSelector also supports custom equality checks so that you have control over when re-renders occur.

Upvotes: 2

Related Questions