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!

Kiper
  • 309
  • 3
  • 17
  • 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. – markerikson Sep 05 '18 at 15:40
  • @markerikson Thank you for replying. Just added the reducer. Maybe you can point something out, but i am using es6 spread and returning new object. – Kiper Sep 06 '18 at 12:13

1 Answers1

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.

markerikson
  • 63,178
  • 10
  • 141
  • 157
  • 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? – Kiper Sep 09 '18 at 07:23
  • After reading the article I could successfully make the selector work. But immer still not working with persist. – Kiper Sep 09 '18 at 07:38
  • 2
    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 ;) ) – Ben Smith Apr 12 '19 at 13:30
  • 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. – Nabeel Hussain Shah Apr 30 '21 at 20:17
  • 1
    [Deep cloning is _not_ the right answer here](https://redux.js.org/faq/performance#do-i-have-to-deep-clone-my-state-in-a-reducer-isnt-copying-my-state-going-to-be-slow), 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. – markerikson May 02 '21 at 03:31