Reselect: Understanding why a selector is run again

Created on 14 Dec 2018  路  8Comments  路  Source: reduxjs/reselect

I'm having difficulty trying to pinpoint why certain selectors are being run again.

There is an action that changes currentMarket in this reducer:

const initialState = fromJS({
  centerList: centerList.results, //  the base list that entire app's data is built around
  daily_appointments: false, // data of appointments for each market (either weekly or quarterly)
  //  ...

  //  ui stuff
  marketModalActive: false,
  currentMarket: false,
  dataType: 'weekly', //  or quarterly
});

What happens is that my selector for daily_appointments and centerList is triggered as well, even though those haven't changed.

I have made sure to immute the objects when the reducer updates, so the selector is indeed receiving an immutable object (and I am calling .toJS()).

Is it possibly because daily_appointments and centerList are on the same level/reducer as currentMarket?

All 8 comments

You'd need to show what your actual selectors are for us to have any idea. That said, this is also a question that is better suited for Stack Overflow.

These are the two selectors in question.

const selectMap = state => state.get('map');

const makeSelectCenterList = () =>
  createSelector(
    selectMap,
    mapState => mapState.get('centerList') && mapState.get('centerList').toJS(),
  );

const makeSelectDailyAppointments = () =>
  createSelector(
    selectMap,
    mapState =>
      mapState.get('daily_appointments') &&
      mapState.get('daily_appointments').toJS(),
  );

Forgive my ignorance, but is there a reason why stack overflow is better for a question like this?

Thanks!

How are you actually using/calling those selectors? Which one is running unexpectedly?

We try to keep the issues focused on actual bug reports, not usage questions. Also, more people will see questions on Stack Overflow, so you're more likely to get an answer.

Looking at this a bit further, I think I see what the issue is.

I'm assuming that map is the first reducer you showed, with center_list and currentMarket in it.

In that case, any update to state.map.currentMarket will create a new reference for state.map. Your other two selectors are using an "input selector" that returns state.map, and the output selectors will re-run any time state.map changes. So yes, I would expect those to be re-running every time you update state.map.currentMarket.

Ah, well that makes perfect sense. So I'll have to lift currentMarket out of that reducer.

Thanks for explaining that succintly and quickly!

No, I would suggest modifying your other selectors so that the "output selectors" only depend on the list values they're trying to extract:

const selectMap = state => state.get('map');

const selectAppointments = createSelector(
    [selectMap],
    (map) => map.get("daily_appointments")
);

const selectAppointmentsJS = createSelector(
    [selectAppointments],
    (appointments) => appointments ? appointments.toJS() : undefined
);

That way, appointments.toJS() will only run when state.map.daily_appointments has actually changed.

I refactored the "output selectors" to only use "input selectors" concerned about their specific list value instead of all of state.map. It solved my issue!

Thank you for walking me through this, feels great to have my app performing as expected.

Not sure if I can bother you here about another question I have but I don't see this explained in the read me:

When should a selector be a factory function, (i.e. const selectFooBar = createSelector(... vs. makeSelectFoorBar = () => createSelector(...?

Is it whenever I need to do any heavy work on the data (transformation/aggregation)? Or is it only when I need to pass an argument to the selector?

I wouldn't use the term "generator", as that has a specific meaning (like function* someGeneratorFunction()). I'd call that a "selector factory".

You really only need to do that if you need to have multiple copies of a selector, each with its own unique cache. This is primarily useful if you have a selector that needs to look up some item by an ID, and you need to make sure that the selector memoizes consistently. See my post Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance for an explanation on this.

Was this page helpful?
0 / 5 - 0 ratings