React & Reselect selector claims state is the same after update
Asked Answered
C

1

5

I am implementing Reselect in my project and have a little confusion on how to properly use it. After following multiple tutorials and articles about how to use reselect, I have used same patterns and still somethings dont work as expected.

My selector:

const getBaseInfo = (state) => state.Info;
const getResources = (state) => state.Resources;

export const ASelector = createSelector(
  [getBaseInfo, getResources],
  (items, resources) => { 
    let result = {};
    for(const item in items) {
      console.log(item);
      result[item] = _.pick(items[item], ['Title', 'Type', 'Beginning', 'minAmount', 'Address'])
    }
    for(const item in resources) {
      console.log(item);
      result[item] = {...result[item], firstImage: resources[item].firstImage}
    }
    return result;
  }
);

mapStateToProps component:

function mapStateToProps(state) {
  console.log(state);
  return { 
    gridInfo: ASelector(state)
  }
}

Now at first my initial state is:

state = { Info: {}, Resources: {} }

My Reducer:

const Info = ArrayToDictionary.Info(action.payload.data.Info);
  const Resources = ArrayToDictionary.Resources(action.payload.data.Info);
  let resourcesKeys = Object.keys(Resources);
  let infoKeys = Object.keys(Info);
  let temp = { ...state };
  let newInfo;

  for (let item of infoKeys) {
    newInfo = {
      Title: Info[item].Title,
      Type: Info[item].Type,
      BeginningOfInvesting: Info[item].BeginningOfInvesting,
      DateOfEstablishment: Info[item].DateOfEstablishment,
      pricePerUnit: Info[item].PricePerUnit,
      minUnits: Info[item].MinUnits,
      publicAmount: Info[item].PublicAmount,
      minInvestmentAmount: Info[item].MinInvestmentAmount,
      EinNumber: Info[item].EinNumber,
      Address: Info[item].Address,
      Status: Info[item].Status,
      Lat: Info[item].Lat,
      Lng: Info[item].Lng,
      CurrencySymbol: Info[item].CurrencySymbol,
      Publicity: Info[item].Publicity
    }
    temp.Info[item] = { ...temp.Info[item], ...newInfo }
  }
  for (let item of resourcesKeys) {
    temp.Resources[item] = { ...temp.Resources[item], ...Resources[item] }
  }
  return temp;

As a component renders with the initial state, I have an action pulling data from api and saving it accordingly into the state inside reducers.

Now my state is changed, but after debugging a little into reselects code, I found in the comparison function that the old and new states are the same. Suddenly my "old" state became already populated with the newState data and it of course failing the comparison as they became the same.

Is there anything wrong with my selectors?

I have really tried to use it as the documentation states, but still cant understand how to solve my little issue.

Thank you very much for reading and helping!

Countdown answered 5/9, 2018 at 7:3 Comment(2)
Can you post your reducers for the state.Info and state.Resources slices? That sounds as if your reducers are mutating the state, instead of updating itimmutably.Longevous
@Longevous Thank you for replying. Just added the reducer. Maybe you can point something out, but i am using es6 spread and returning new object.Countdown
L
7

It looks like the temp.Info[item] and temp.Resources[item] lines are mutating the existing state. You've made a shallow copy of the top level, but aren't correctly copying the second level. See the Immutable Update Patterns page in the Redux docs for an explanation of why this is an issue and what to do instead.

You might want to try using the immer library to simplify your immutable update logic. Also, our new redux-starter-kit library uses Immer internally.

Longevous answered 6/9, 2018 at 15:40 Comment(5)
Thank you for replying. I have installed immer, and while trying to use it found that my redux-persist library is mutating the state to add ._persist which returns exception because draft isnt extensible. Do you know anything about this?Countdown
After reading the article I could successfully make the selector work. But immer still not working with persist.Countdown
I had a similar problem and your answer helped. I was copying an array using the spread syntax, but that does a shallow copy, and hence when I was updating it I was mutating state. Doh! (and its not the first time I've done that). Lodash's cloneDeep came to my rescue. Leaving this here for anyone else with the same issue (or my future self ;) )Huntingdon
using deepClone form lodash will definitely work, it can be used if performance is not super critical for the app as cloning the whole object does take some processing power and time, but for normal use cases this is an option incase someone doesn't understand other concepts.Sundberg
Deep cloning is not the right answer here, because it will cause unnecessary re-renders. Also, it's a hack workaround, much like throwing in random setTimeouts to fix timing bugs. Please don't do that.Longevous

© 2022 - 2025 — McMap. All rights reserved.