Redux deep clone - state is always equal

Viewed 1471

I have the following reducer in React Redux:

export const reducer = (state = initialStateData, action) => {
    switch (action.type) {
      case Action.TOGGLE_ARR_FILTER:
        {
          const subArr = state.jobOffers.filters[action.key];
          const filterIdx = subArr.indexOf(action.value[0]);
          const newArr = { ...state.jobOffers.filters
          };

          if (filterIdx !== -1) {
            newArr[action.key].splice(filterIdx, 1);
          } else {
            newArr[action.key].push(action.value[0]);
          }

          return {
            ...state,
            jobOffers: {
              ...state.jobOffers,
              filters: {
                ...newArr,
              },
            },
          };
        }

And this is my object:

const initialStateData = {
  jobOffers: {
    filters: {
      employments: [],
      careerLevels: [],
      jobTypeProfiles: [],
      cities: [],
      countries: [],
    },
    configs: {
      searchTerm: '',
      currentPage: 1,
      pageSize: 5,
    },
  },
};

The reducer as such seems to work, it toggles the values correctly.

But: Redux always shows "states are equal", which is bad as it won't recognize changes.

Can someone help ? I assume that I am returning a new object..

2 Answers

you can use Immer , redux also uses this for immutable updates for nested stuffs. Because of this, you can write reducers that appear to "mutate" state, but the updates are actually applied immutably.

const initialStateData = {
  jobOffers: {
    filters: {
      employments: [],
      careerLevels: [],
      jobTypeProfiles: [],
      cities: [],
      countries: [],
    },
    configs: {
      searchTerm: '',
      currentPage: 1,
      pageSize: 5,
    },
  },
};
export const reducer = immer.produce((state = initialStateData, action) => {
    switch (action.type) {
      case Action.TOGGLE_ARR_FILTER:

            const subArr = state.jobOffers.filters[action.key];
            const filterIdx = subArr.indexOf(action.value[0]);
            const newArr = state.jobOffers.filters;

            if (filterIdx !== -1) 
              newArr[action.key].splice(filterIdx, 1);
            else 
              newArr[action.key].push(action.value[0]);

            return state;
           }
          })        

Although you take a copy of state.jobOffers.filters, this still holds references to original child arrays like employments. So when you mutate newArr[action.key] with splice or push, Redux will not see that change, as it is still the same array reference.

You could replace this:

if (filterIdx !== -1) {
  newArr[action.key].splice(filterIdx, 1);
} else {
  newArr[action.key].push(action.value[0]);
}

with:

newArr[action.key] = filterIdx !== -1 
    ? [...newArr[action.key].slice(0, filterIdx), ...newArr[action.key].slice(filterIdx+1)]
    : [...newArr[action.key], action.value[0]]);

BTW, you don't have to copy newArr again, as it already is a copy of filters. You can replace:

filters: {
    ...newArr,
},

by just:

filters: newArr,
Related