I'm confused about the arguments that i passe to a selector function that uses reselect inside containers

1.1k views Asked by At

I'm learning the usage of reselect, but i'm confused about the way i make use of the memoized selector in inside my container.

I've got a component called Summary that received 3 props thats are subtotal, tipAmount , total .

It looks like this export const Summary = ({ subtotal = 0, tipAmount = 0, total = 0 })=>{...}.And those props are being inject my the connect HOC inside a dedicated container that i've called SummaryContainer with the following code.

import { connect } from 'react-redux';
import { Summary } from '../components/Summary';
import {
  selectTipAmount,
  selectSubtotal,
  selectTotal
} from '../store/items/selectors';

const mapStateToProps = (state) => {
  const subtotal = selectSubtotal(state);

  const tipAmount = selectTipAmount(state);
  const total = selectTotal(state);

  return {
    subtotal,
    tipAmount,
    total
  };
};

export const SummaryContainer = connect(mapStateToProps)(Summary);

As you can see this code uses 3 selectors selectTipAmount , selectSubtotal and selectTotal that i'm importing from a selector file with the follwing code.

import { createSelector } from 'reselect';

const selectItems = (state) => state.items;
const selectTipPercentage = (state) => state.tipPercentage;


export const selectSubtotal = createSelector([selectItems], (items) =>
  items.reduce((acc, { price, quantity }) => acc + price * quantity, 0)
);

export const selectTipAmount = createSelector(
  [selectSubtotal, selectTipPercentage],
  (subtotal, tipPercentage) => subtotal * (tipPercentage / 100)
);

export const selectTotal = createSelector(
  [selectSubtotal, selectTipAmount],
  (subtotal, tipAmount) => subtotal + tipAmount
);


export const selectItemWithTotal = createSelector([selectItems], (items) =>
  items.map((item) => ({ ...item, total: item.price * item.quantity }))
);


export const selectItem = (state, props) =>
  state.items.find((item) => item.uuid === props.uuid);

export const selectItemTotal = createSelector(
  [selectItem],
  (item) => item.price * item.quantity
);

So here is my problem :

When i call write const total = selectTotal(state);inside mapStateToProps i passe to the total constant the results of the execution of selectTotal(state) selector and when i look at the argument, it's state.It looks like i'm invoking the selectTotal selector by passing and state argument, but in the actuel implementation it isn't a function that takes a state param.In the actual implementation that selection equals the result of createSelect function like this:

export const selectTotal = createSelector(
  [selectSubtotal, selectTipAmount],
  (subtotal, tipAmount) => subtotal + tipAmount
);

I haven't seen where the state argument is being passed.What is going on?

The code works perfectly but i'm lost about where the state argument is being used.

When i look at selectItems selector it makes sens, but with selectors that are created with createSelector i'm lost.

2

There are 2 answers

0
Drew Reese On

I find it easiest to think of reselect selectors as sort of the "opposite" of redux reducer functions. With reducer functions each reducer owns a chunk of state and when they are combined they form a reducer tree where once they are all combined form the entire state object.

The selectors are basically doing the opposite. They start with simple selectors that take the entire state object and pluck out chunks of it. These sub-state chunks can then be fed into created selectors to pluck out smaller chunks (or compute derived state). The inputs used in createSelector are a sort of "dependency", when one of them updates then the selector recomputes its value.

createSelector is essentially a higher order function that consumes "dependencies" and a result function, and returns a function that consumes the state object. When you call selectSubtotal and pass state you are really passing state to a selector tree (or set of trees).

For example, when state.items updates the selectItems selector that is an input to selectSubtotal will update its return value. This update will trigger selectSubtotal to then recompute its value, and so on for any other selectors that use selectSubtotal as an input.

If you are curious, the resultant selector function will also have a resultFunc property that matches the computing function originally passed to createSelector. For example, selectTipAmount.resultFunc will be (subtotal, tipPercentage) => subtotal * (tipPercentage / 100), called like selectTipAmount.result.Func(subTotal, tipPercentage);. I use this heavily when unit testing my selectors since I really don't care about memoizing a value, but rather that they correctly compute derived state; I won't need to mock up an entire state object that matches the selector dependencies.

0
HMR On

The createSelector function returns a function so in const mySelectorFunction = createSelector... the value mySelectorFunciton is a function.

A simple implementation of createSelector without memoization would be:

const createSelector = (functions=[],calculatorFunction) => {
  // you could do memoize(calculatorFunction) to implement memoization
  //the function returned is the selectOnePlusTwo in demo below
  return (...args) =>
    // when this function is called then it will call functions with args
    //  and pass the returning values to calculatorFunction
    calculatorFunction(...functions.map(fn=>fn(...args)))
}
// demo createSelector
const state = { one:1,two:2 }
const selectOne = state=>state.one
const selectTwo =  state=>state.two
const selectOnePlusTwo = createSelector(
  [selectOne,selectTwo],
  (one,two)=>one+two
)
console.log(
  'one plus two:',
  selectOnePlusTwo(state)
)

Memoization is a nice touch that could make your code slightly more performant by skipping virtual DOM compare but what usually causes havoc on your performance is passing in a new handler reference that will cause this comparison to fail and make React re paint that DOM, for example <SomeComponent onClick={()=>newRefCreated} /> will re paint whatever SomeComponent generates because the onClick changes every time. This is why there is a useCallback hook.

I'd say that the power of createSelector comes from easily re usable code and preventing duplicate implementation. If in the example above state.one would change to a string I need to only refactor the selectOne function and all other selectors depending on state.one would still work.