Search code examples
typescriptreduxmemoizationreselectmemoized-selectors

Redux/reselect selector function arguments explanation?


I am working on a codebase written in Typescript that uses a lot of immutable reselect. I haven't been working with reselect yet and I have come across an example that I don't understand:

export const panelsSelector: (
  order: Order,
  events: List<List<Event>>,
  adminRole: boolean,
  packageEvents: List<List<PackageEvent>>,
) => List<PanelProps> = createImmutableEqualSelector(
  (order: Order) => order,
  (order: Order, events: List<List<Event>>) => events,
  (order: Order, events: List<List<Event>>, adminRole: boolean) => adminRole,
  (order: Order, events: List<List<Event>>, adminRole: boolean, packageEvents: List<List<PackageEvent>>) => packageEvents,
  (order, events, adminRole, packageEvents) => List<PanelProps>([
      {
        content: <Events packageEvents={packageEvents}
                         events={events}
                 />
      },
      {
        content: <MyComponent  packages={order.get('packages')}
                               adminRole={adminRole}
                               packagesCount={order.get('noOfPackages')}/>
      },
)

If I am reading this right panelsSelector is assigned I guess an anonymous type which is a function. In this case I guess createImmutableEqualSelector returns a function which returns List<PanelProps>. To simplify something like this:

const panelsSelector:(arg) => List<PanelProps> = createImmutableEqualSelector((arg) => List<PanelProps>)

What I don't understand in this example is why the selectors need to have an argument added from its predecessor to an argument list?

  (order: Order) => order,
  (order: Order, events: List<List<Event>>) => events,
  (order: Order, events: List<List<Event>>, adminRole: boolean) => adminRole,
  (order: Order, events: List<List<Event>>, adminRole: boolean, packageEvents: List<List<PackageEvent>>) => packageEvents

If I remove an argument from a predecessor for example for the second selector:

(events: List<List<Event>>) => events

I get a typescript error:

Types of parameters 'state' and 'order' are incompatible.

So, why do this selectors need to be written like that? So far I have only seen simple examples like this one where we get the data from the state:

const selectShopItems = state => state.shop.items

But, I don't understand the former example. If order is not state, then where is that data coming from?


Solution

  • When you call a Reselect selector, like selectThing(state, 2, 3, 4), Reselect will then call each of the input selectors with each of those same inputs:

    const input1Result = inputSelector1(state, 2, 3, 4);
    const input2Result = inputSelector2(state, 2, 3, 4);
    const input3Result = inputSelector3(state, 2, 3, 4);
    // etc
    

    Reselect then passes each of those results to the final "output selector":

    const finalResult = outputFunction(input1Result, input2Result, input3Result);
    

    So, all of the input functions will be called with the exact same arguments.

    It's very common to have one or two input selectors that extract data from the Redux state, but also to extract some of the additional parameters as well:

    const selectItemId = (state, itemId) => itemId;
    

    In this case, whoever wrote panelsSelector decided not to explicitly list all four parameters for all of the input selectors. That's fine, because JS allows you to call a function with more parameters than it actually needs - they just need to all line up correctly (ie, all of them should expect events to be the second argument).

    I do have a couple observations about this specific selector:

    First, it shouldn't be necessary to explicitly specify the type of panelsSelector. TS will know that it's a function that takes those arguments and returns that result.

    Second, having a selector that returns JSX feels... kinda sketchy? It'll work, but I personally would try to avoid that. I'd want to return just a data structure, and keep the JSX part in the actual component.

    Note that the latest version of Reselect (4.1.x) has much improved TS types as well.

    And finally, we recommend against using Immutable.js at all today with Redux, and especially given that our official Redux Toolkit package builds in use of Immer for immutable updates.

    You may also want to read the Redux docs page on Deriving Data With Selectors.