website/docs/api/weakMapMemoize.mdx
import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem' import { InternalLinks } from '@site/src/components/InternalLinks' import { ExternalLinks } from '@site/src/components/ExternalLinks'
weakMapMemoize<InternalLinks.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 <ExternalLinks.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.
Pros:
Cons:
useSelector(state => selectSomeData(state, id))
Prior to weakMapMemoize, you had this problem:
<Tabs groupId='language' defaultValue='ts' values={[ {label: 'TypeScript', value: 'ts'}, {label: 'JavaScript', value: 'js'}, ]}> <TabItem value='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!
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:
maxSize with <InternalLinks.LruMemoize />:<Tabs groupId='language' defaultValue='ts' values={[ {label: 'TypeScript', value: 'ts'}, {label: 'JavaScript', value: 'js'}, ]}> <TabItem value='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
}
}
)
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.
<Tabs groupId='language' defaultValue='tsx' values={[ {label: 'TypeScript', value: 'tsx'}, {label: 'JavaScript', value: 'jsx'}, ]}> <TabItem value='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>
)
}
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>
)
}
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.
<Tabs groupId='language' defaultValue='ts' values={[ {label: 'TypeScript', value: 'ts'}, {label: 'JavaScript', value: 'js'}, ]}> <TabItem value='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!
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.
| Name | Description |
|---|---|
func | The function to be memoized. |
A memoized function with a .clearCache() method attached.
| Name | Description |
|---|---|
Func | The type of the function that is memoized. |
weakMapMemoize with createSelector<Tabs groupId='language' defaultValue='ts' values={[ {label: 'TypeScript', value: 'ts'}, {label: 'JavaScript', value: 'js'}, ]}> <TabItem value='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')
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')
weakMapMemoize with createSelectorCreator<Tabs groupId='language' defaultValue='ts' values={[ {label: 'TypeScript', value: 'ts'}, {label: 'JavaScript', value: 'js'}, ]}> <TabItem value='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')
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')