Should we compare new state with old state in the reducer

Viewed 1250

I am using spread operator in the reducer function, so what is happening when i am dispatching the action then it is creating new state (obj) even old state is same as like new state so it is re-rendering react component.

Is it good approach to compare new state with old state as object could be complex ? Deep comparison ?

const initialState = {};

export const reducer = (state = initialState, action: any = {}): any => {
  if (action.type === ActionTypes.RC) {
    return {
      ...state,
      ...action.config,
    };
  }
  return state;
};

Or redux does it out of the box for you in mapStateToProps ?

2 Answers

There a few things worth noting here.

React Redux uses strict equality (reference equality) as the default method for comparisons. The immutable nature of Redux state means that any change to a deeply-nested property requires that all of its ancestor must be new object references.

If you update a.b.c then a and a.b must be new objects. Let's say that you have a sibling property tree like a.d.e. When you update a.b.c it causes a to be a new object reference, but a.d and a.d.e are the same. If you have a component that only needs to know about a.d then it should not need to re-render in response to your a.b.c change.

This is why it's important that each component should only select the minimal information that it needs from the state. You should connect more individual components rather than selecting a lot of information from state and passing it down through props. You can use multiple useSelector hooks in the same component to select small, granular pieces of state.


You do have the ability to customize the equality comparison by passing a comparison function as the second argument to the useSelector hook.

If you have a component that subscribes to the piece of state that's controlled by this reducer then you can use the shallowEqual function which is included in the react-redux package. I'm not sure if the reducer in your question is your root reducer or something that you are passing to combineReducers. I'm assuming it's just one piece. Let's say it's for a property settings and you have some updateSettings action that merges the new settings with the existing ones. Then when you have a component that needs to access the settings, you would do this:

import { shallowEqual, useSelector } from 'react-redux'
const settings = useSelector(state => state.settings, shallowEqual);

This would mean that the component will only re-render if some of the settings have actually changed, even if state.settings is a new object. It is doing a shallow comparison between the current value of state.settings and the previous one.

It is possible to use a deep equals comparison like lodash's isEqual but I'm not seeing why you would need it as this reducer is making a shallow copy. If you need to do that expensive check anywhere then it seems more logical to do it before you dispatch the action in the component or before you return a new object from the reducer rather than on every call of the useSelector hook.

You asked about whether you should check for changes in the reducer before returning a new state object. That's a good idea and it's actually what a combined reducer created by combineReducers does, though it only checks for shallow equality. See lines 177-201 of the source code. If all of the individual property reducers returned the same object as before then it will return the previously combined state in order to maintain object references.

personal opinion:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);// <= re-render state diff here

The page should be re-render or not is decided by react, or says: will be better.

There's dom-diff algorithm in react.

You dispatch action from multiple pages, these pages pick their own state from newState, and diff themselves, trigger re-render or not.

So i think you don't have to run another diff algorithm in reducer.

consider case:

You have PageA & PageAB, state { a:1, b:1 }

PageA re-render by a. PageAB re-render by a,b

Now your state become {a:1, b:2}. How could you make PageA not re-render in reducer? you will diff it in PageA.

Related