Skip to main content

weakMapMemoize

lruMemoize has to be explicitly configured to have a cache size larger than 1, and uses an LRU cache internally.

weakMapMemoize creates a tree of WeakMap-based cache nodes based on the identity of the arguments it's been called with (in this case, the extracted values from your input selectors). This allows weakMapMemoize to have an effectively infinite cache size. Cache results will be kept in memory as long as references to the arguments still exist, and then cleared out as the arguments are garbage-collected.

Design Tradeoffs

  • Pros:

    • It has an effectively infinite cache size, but you have no control over how long values are kept in cache as it's based on garbage collection and WeakMaps.
  • Cons:

Use Cases

  • This memoizer is likely best used for cases where you need to call the same selector instance with many different arguments, such as a single selector instance that is used in a list item component and called with item IDs like:
useSelector(state => selectSomeData(state, id))

Prior to weakMapMemoize, you had this problem:

weakMapMemoize/cacheSizeProblem.ts
import { createSelector } from 'reselect'

export interface RootState {
items: { id: number; category: string; name: string }[]
}

const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' },
],
}

const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category,
],
(items, category) => items.filter(item => item.category === category),
)

selectItemsByCategory(state, 'Electronics') // Selector runs
selectItemsByCategory(state, 'Electronics')
selectItemsByCategory(state, 'Stationery') // Selector runs
selectItemsByCategory(state, 'Electronics') // Selector runs again!

Before you could solve this in a number of different ways:

  1. Set the maxSize with lruMemoize:
weakMapMemoize/setMaxSize.ts
import { createSelector, lruMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'

const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category,
],
(items, category) => items.filter(item => item.category === category),
{
memoize: lruMemoize,
memoizeOptions: {
maxSize: 10,
},
},
)

But this required having to know the cache size ahead of time.

  1. Create unique selector instances using useMemo.
weakMapMemoize/withUseMemo.tsx
import type { FC } from 'react'
import { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
import type { RootState } from './cacheSizeProblem'

const makeSelectItemsByCategory = (category: string) =>
createSelector([(state: RootState) => state.items], items =>
items.filter(item => item.category === category),
)

interface Props {
category: string
}

const MyComponent: FC<Props> = ({ category }) => {
const selectItemsByCategory = useMemo(
() => makeSelectItemsByCategory(category),
[category],
)

const itemsByCategory = useSelector(selectItemsByCategory)

return (
<div>
{itemsByCategory.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
)
}
  1. Use Re-reselect:
import { createCachedSelector } from 're-reselect'

const selectItemsByCategory = createCachedSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category,
],
(items, category) => items.filter(item => item.category === category),
)((state: RootState, category: string) => category)

Starting in 5.0.0, you can eliminate this problem using weakMapMemoize.

weakMapMemoize/cacheSizeSolution.ts
import { createSelector, weakMapMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'

const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' },
],
}

const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category,
],
(items, category) => items.filter(item => item.category === category),
{
memoize: weakMapMemoize,
argsMemoize: weakMapMemoize,
},
)

selectItemsByCategory(state, 'Electronics') // Selector runs
selectItemsByCategory(state, 'Electronics') // Cached
selectItemsByCategory(state, 'Stationery') // Selector runs
selectItemsByCategory(state, 'Electronics') // Still cached!

This solves the problem of having to know and set the cache size prior to creating a memoized selector. Because weakMapMemoize essentially provides a dynamic cache size out of the box.

Parameters

NameDescription
funcThe function to be memoized.

Returns

A memoized function with a .clearCache() method attached.

Type Parameters

NameDescription
FuncThe type of the function that is memoized.

Examples

Using weakMapMemoize with createSelector

weakMapMemoize/usingWithCreateSelector.ts
import { createSelector, weakMapMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'

const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' },
],
}

const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category,
],
(items, category) => items.filter(item => item.category === category),
{
memoize: weakMapMemoize,
argsMemoize: weakMapMemoize,
},
)

selectItemsByCategory(state, 'Electronics') // Selector runs
selectItemsByCategory(state, 'Electronics')
selectItemsByCategory(state, 'Stationery') // Selector runs
selectItemsByCategory(state, 'Electronics')

Using weakMapMemoize with createSelectorCreator

weakMapMemoize/usingWithCreateSelectorCreator.ts
import { createSelectorCreator, weakMapMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'

const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' },
],
}

const createSelectorWeakMap = createSelectorCreator({
memoize: weakMapMemoize,
argsMemoize: weakMapMemoize,
})

const selectItemsByCategory = createSelectorWeakMap(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category,
],
(items, category) => items.filter(item => item.category === category),
)

selectItemsByCategory(state, 'Electronics') // Selector runs
selectItemsByCategory(state, 'Electronics')
selectItemsByCategory(state, 'Stationery') // Selector runs
selectItemsByCategory(state, 'Electronics')