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
WeakMap
s.
- 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
-
Cons:
- There's currently no way to alter the argument comparisons. They're based on strict reference equality.
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:
- TypeScript
- JavaScript
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!
import { createSelector } from 'reselect'
const state = {
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 => state.items, (state, category) => 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:
- Set the
maxSize
withlruMemoize
:
- TypeScript
- JavaScript
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
}
}
)
import { createSelector, lruMemoize } from 'reselect'
const selectItemsByCategory = createSelector(
[state => state.items, (state, category) => 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.
- Create unique selector instances using
useMemo
.
- TypeScript
- JavaScript
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>
)
}
import { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
const makeSelectItemsByCategory = category =>
createSelector([state => state.items], items =>
items.filter(item => item.category === category)
)
const MyComponent = ({ category }) => {
const selectItemsByCategory = useMemo(
() => makeSelectItemsByCategory(category),
[category]
)
const itemsByCategory = useSelector(selectItemsByCategory)
return (
<div>
{itemsByCategory.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
)
}
- 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
.
- TypeScript
- JavaScript
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!
import { createSelector, weakMapMemoize } from 'reselect'
const state = {
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 => state.items, (state, category) => 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
Name | Description |
---|---|
func | The function to be memoized. |
Returns
A memoized function with a .clearCache()
method attached.
Type Parameters
Name | Description |
---|---|
Func | The type of the function that is memoized. |
Examples
Using weakMapMemoize
with createSelector
- TypeScript
- JavaScript
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')
import { createSelector, weakMapMemoize } from 'reselect'
const state = {
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 => state.items, (state, category) => 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
- TypeScript
- JavaScript
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')
import { createSelectorCreator, weakMapMemoize } from 'reselect'
const state = {
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 => state.items, (state, category) => 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')