A standard reselect selector invalidates its memoized value and recomputes it if the input selectors fail a strict equality check:
export const selectEmailsFromComments = createSelector(
selectComments, // returns an array of comments
comments => commentsToEmails(comments)
)
Since the comments are an array and not a primitive value, and redux reducers tend to create a new piece of state to avoid side effects, the above seems to never actually memoize because the comments array returned by selectComments
will always have a different reference.
To resolve this, one can create a custom selector creator, e.g. to introduce shallow equality checks:
const createShallowEqualSelector = createSelectorCreator(
defaultMemoize,
shallowEqual
)
export const selectEmailsFromComments = createShallowEqualSelector(
selectComments, // returns an array of comments
comments => commentsToEmails(comments)
)
This works if the comments indeed are simple objects and we want to recompute the emails whenever any of the comments' props changed.
But what if we only want to recompute the emails if e.g. the number of comments changed? How could we implement a custom equality check? I would expect the following to work:
type ComparisonFunction<B extends object = {}> = (prev: B, next: B, index: number) => boolean
const createCustomEqualSelector = <B extends object = {}>(
equalFn: ComparisonFunction<B>
) => createSelectorCreator<ComparisonFunction<B>>(defaultMemoize, equalFn)
const commentsEqualFn = (a: IComment[], b: IComment[], index: number) =>
a.length === b.length
export const selectEmailsFromComments = createCustomEqualSelector(
commentsEqualFn
)(
selectComments, // returns an array of comments
comments => commentsToEmails(comments)
)
However this returns the following Typescript error for defaultMemoize:
(alias) function defaultMemoize<F extends Function>(func: F, equalityCheck?: (<T>(a: T, b: T, index: number) => boolean) | undefined): F
import defaultMemoize
Argument of type '<F extends Function>(func: F, equalityCheck?: (<T>(a: T, b: T, index: number) => boolean) | undefined) => F' is not assignable to parameter of type '<F extends Function>(func: F, option1: ComparisonFunction<B>) => F'.
Types of parameters 'equalityCheck' and 'option1' are incompatible.
Type 'ComparisonFunction<B>' is not assignable to type '<T>(a: T, b: T, index: number) => boolean'.
Types of parameters 'prev' and 'a' are incompatible.
Type 'T' is not assignable to type 'B'.ts(2345)
How would I resolve this type error for a custom reselect createSelector equality function?
Reselect exampleimport { createSelector } from 'reselect' const getTodosSelector = state => state. todos. todoList const getTodosWithLetter = createSelector( getTodosSelector, (state,Letter)=>Letter, (toDos,Letter) => toDos. filter(todo.
Reselect provides a function called createSelector to generate memoized selectors. createSelector accepts one or more "input selector" functions, plus an "output selector" function, and returns a new selector function for you to use.
Example: // get all posts export const selectPosts = (state: TState): TPostsState => state. posts; // get new posts export const selectNewPosts = createSelector< [Selector<TState, TPostsState>], TPostData[] >( selectPosts, (posts) => posts. filter(({ type }) => type === 'new'), );
To call the createSelector function, start by passing in one or more basic selector functions like the getProducts selector that we looked at earlier. The last parameter that createSelector takes is a function to further process the results of the supplied selector (or selectors).
The following worked for me but I'm not sure if it's the best way.
import {
createSelectorCreator,
defaultMemoize,
} from 'reselect';
type IComment = { id: number };
type State = { comments: IComment[] };
type CompareFn = <T>(a: T, b: T, index: number) => boolean;
const createCustomEqualSelector = (equalFn: CompareFn) =>
createSelectorCreator(defaultMemoize, equalFn);
const selectComments = (state: State) => state.comments;
const commentsEqualFn: CompareFn = (a, b, index) =>
//need to cast it or get an error:
// Property 'length' does not exist on type 'T'
((a as unknown) as IComment[]).length ===
((b as unknown) as IComment[]).length;
export const selectEmailsFromComments = createCustomEqualSelector(
commentsEqualFn
)(
selectComments, // returns an array of comments
(comments) => {
console.log('calculating comments:', comments);
return comments;
}
);
selectEmailsFromComments({ comments: [{ id: 1 }] })
//this will log previous value because array length didn't change
console.log('memoized', selectEmailsFromComments({ comments: [{ id: 2 }] }))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With