Search code examples
javascriptreactjsreact-reduxreselect

How to access state, without depending on it with createSelector?


I have a list of entities, each with the property active on it. I want to select those that are active, and to avoid rerendering them each time entities is updated (as an entity which is not active may be added). I figured I could keep track of activeIds, and update it when an entity becomes active.

My issue is, to grab the active entities, I would guess to write something like:

const selectActiveEntities = createSelector(
  (state) => state.entities.items,
  (state) => state.entities.activeIds,
  (entities, activeIds) => activeIds.map(id => entities[id])
)

However, this creates an issue! I believe it will re-render whenever state.entities.items is updated. I, the programmer can identify that the output won't change unless state.entities.activeIds explicitly changes. How do I achieve dependence solely on state.entities.activeIds?


Solution

  • I believe it will re-render whenever state.entities.items is updated.

    Yes, kind of. But don't think of these a "re-renders" here just yet though, think of these as inputs and outputs. When any input to a selector function updates its value, it will trigger the selector to re-compute its output value. This should be expected and desired behavior.

    I, the programmer can identify that the output won't change unless state.entities.activeIds explicitly changes.

    You might know this, but the selector function can't. All it has to go by are the inputs. This is also simply not true. Either of the input values can change. As stated above, any time any input value changes, the selector will re-compute its output. It doesn't know yet that just because only the active ids changed that the output will be the same... it just recomputes and memoizes the output value, which just might happen to be the same as the previous output.

    How do I achieve dependence solely on state.entities.activeIds?

    You can't really, since you are computing a derived state that depends on both state.entities.activeIds and state.entities.items, both values are used as inputs for the output you are computing.

    const selectActiveEntities = createSelector(
      [
        (state) => state.entities.items,     // <-- items input
        (state) => state.entities.activeIds, // <-- activeIds input
      ],
      (items, activeIds) => activeIds.map(   // --> filtered activeIds output
        id => items[id]
      )
    );
    

    If you like, you can reduce the code a bit and use state.entities as the input value, but this then depends on the entire state.entities object value instead of just the activeIds and items values.

    const selectActiveEntities = createSelector(
      [(state) => state.entities],
      (entities) => entities.activeIds.map(id => entities.items[id])
    );
    

    Because you are working with arrays, and as was suggested in your other post regarding a similar question around selectors and component rerendering, if the UI components selecting these active entities still needs a boost you should use the shallowEqual function.

    import { shallowEqual, useSelector } from 'react-redux';
    
    ...
    
    const activeEntities = useSelector(selectActiveEntities, shallowEqual);