I am working on building a custom selector using the createSelector from reselect. The input selectors will be returning data that will fail the default === comparison. To make sure I understood the behavior I am building some fundamental tests that return arrays from the input selectors.
I am expecting the results will be recomputed with every call to the selector, but that is not what I am seeing. The function is not recomputing even when I had memoizeOptions that ensure the input selector comparisons return false.
I am not sure what I am doing wrong or what fundamental mistake I am making in this test that is tripping me up. I would appreciate any help getting this figured out.
I am using Jest v27.4.7, node v15.12.0, with reselect 4.1.6 on Linux.
import {test, expect} from '@jest/globals';
import { createSelector } from "reselect";
test("createSelector:array", async () => {
const a: number[] = [1, 2, 3, 4, 5];
const b: string[] = ["a", "b", "c", "d", "e"];
const state0: { a: number[], b: string[] } = { a, b };
// https://github.com/reduxjs/reselect#reselect
// > createSelector determines if the value returned by an input-selector has changed between calls
// > using reference equality (===). Inputs to selectors created with createSelector should be immutable.
const select = createSelector(
(state, index: number): number[] => state.a.slice(0, index),
(state, index: number): string[] => state.b.slice(0, index),
(a_in: number[], b_in: string[]): (number | string)[] => [...a_in, ...b_in],
{ memoizeOptions: { equalityCheck: (a, b) => { console.debug("equalityCheck", a, b); return false }, resultEqualityCheck: (a, b) => { console.debug("resultEqualityCheck", a, b); return false } } } // THESE DO NOTHING, WHY?
);
let out = select(state0, 2);
expect(out).toEqual([1, 2, "a", "b"]);
expect(select.recomputations()).toEqual(1);
out = select(state0, 2);
expect(out).toEqual([1, 2, "a", "b"]);
expect(select.recomputations()).toEqual(2); // THIS IS 1, WHY?
});
Per the official reselect documentation:
So here is how reselect works internally, when you call a selector a second time, reselect will first check to see if the arguments that you passed in to the selector have changed since last time, in your case,
state0and2. if they have it will then traverse your dependencies (the input selectors), run each one and check to see if their results have changed since last time, and if they haven't, it will skip running your output selector (or resultFunc), and if they have changed it will run your output selector and the recomputations counter gets incremented. So it has 2 layers of checks:In your case the reason why your selector doesn't recompute is that your selector is passing the first check, you're calling the selector itself with the exact same arguments twice. So it doesn’t recompute. You could do something like this:
equalityCheckis used to check the return values of your input selectors with the previous ones, so it is used to do the second layer of checks.resultEqualityCheckis used to check the current result of your output selector with the previous one. The best way to test it is to do this:So the order of execution is:
equalityCheckruns on the result of each input selector.resultEqualityCheckruns on the result of the output selector.I understand your confusion, because in reselect you can’t customize the equality check function for the first layer of checks, reselect uses reference equality checks to see if the arguments passed into the selector itself have changed and as of now does not allow you to customize it. So you can rewrite your tests to look more like this: