NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #1358 most downloaded on npm
Selectors for Redux.
Last release 1 months ago
22 Aug 2026
Release timing varies
gaps range from 2 weeks to 2.0 years
Nearly every release is documented
notes for 34 of 36 stable releases
Nothing withdrawn
no release was ever pulled
11 years old
55 releases · first in 2015
This feature release adds a maxSize option to weakMapMemoize to bound cache growth, adds a new development-mode check that warns when a selector's cac
This feature release adds a maxSize option to weakMapMemoize to bound cache growth, adds a new development-mode check that warns when a selector's cache grows without bound, improves memoization performance, removes the experimental unstable_autotrackMemoize, and updates our TypeScript support matrix and documentation.
maxSize Option for weakMapMemoizeweakMapMemoize has been the default memoizer since v5.0, and its main benefit is the infinite cache size. Prior to v5, selector instances had a default cache size of 1, which meant having to create unique selector instances per component when sharing selectors that took varying arguments like IDs. weakMapMemoize memoizes based on all arguments, so it eliminated the extra setup work and just stores all cached values.
However, that behavior can also effectively turn into a memory leak depending on what state and arguments are passed in and how they're used in the UI.
weakMapMemoize now accepts a maxSize option that bounds this growth:
const getVisibleItems = weakMapMemoize(
(items, from, to) => items.slice(from, to),
{ maxSize: 100 }
)This shares the same maxSize option name as the earlier lruMemoize, but has different behavior. Rather than an LRU eviction, this is a "generational" swap (kind of like a double buffer). When the cache hits its max size, it's swapped out with an empty version and starts over at 0 entries. So, there's effectively at most 2 * maxSize items in memory at any time. If maxSize is not enabled, there's no additional logic or performance overhead. If you do need LRU-style behavior, use lruMemoize instead.
Note that bounding a createSelector selector requires passing maxSize in both memoizeOptions and argsMemoizeOptions, since the two memoization levels have separate caches. See the maxSize docs for details.
Thanks to @veksa for proposing this in PR #761 and providing memory test infrastructure that helped verify this behavior.
cacheSizeCheck Dev-Mode CheckWe've also added an additional dev-mode check alongside the existing inputStabilityCheck and identityFunctionCheck. If a memoized function has accumulated over 1000 values for the same args, it logs a warning with the function name and stack trace. By default this runs once per function. Unlike the other two checks, it can only be configured globally, via setGlobalDevModeChecks({ cacheSizeCheck: 'always' | 'once' | 'never' })
We've revamped our own performance benchmark suite to give better results with more precision and less noise. That's helped verify some additional performance improvements.
weakMapMemoize now returns early on a cache hit instead of continuing through bookkeepinglruMemoize's cache lookup was simplified to eliminate unnecessary allocationsThese are small wins on already-fast paths, but did show modest improvements.
unstable_autotrackMemoizeWe've removed the experimental unstable_autotrackMemoize export. It was added in v5.0 as an experiment in Glimmer-style dependency tracking, never left unstable_, and as far as we can tell never saw real adoption. If you were using it, switch to weakMapMemoize (the default) or lruMemoize.
Our TypeScript support matrix is now 5.6 and up, matching DefinitelyTyped, and CI now tests against TS 6.0.
We've documented on the selector fields that a full cache reset requires clearing both memoization levels: selector.clearCache() plus selector.memoizedResultFunc.clearCache().
We improved types handling in cases where TS strict mode is off (but please migrate to strict behavior as soon as possible!)
The API docs got a structural overhaul: every API page now follows a consistent "API Reference / Usage Guide" layout, the dev-checks page was rewritten, and the docs reflect that weakMapMemoize is the default memoizer since v5. We've also added some additional usage guidance as well.
createSelector by @veksa in #770resultEqualityCheck receiving a cleared WeakRef by @veksa in #763maxSize option for weakMapMemoize by @markerikson in #783Full Changelog: v5.2.0...v5.3.0
One column per quarter.
This feature release improves tree-shaking and fixes a potential ref leak in weakMapMemoize , as well as restoring trusted publishing provenance.
This feature release improves tree-shaking and fixes a potential ref leak in weakMapMemoize, as well as restoring trusted publishing provenance.
We've restructured some of the internals to improve tree-shaking.
We've fixed a rare case that could lead to weakMapMemoize leaking a WeakRef.
We had set up trusted publishing for Reselect a couple years ago and did some releases with that enabled, but at some point I did a follow-up release that still used the previous manual workflow, and that lost the trusted publishing provenance flag. We had recent issues requesting a new release with trusted publishing enabled again, so we've fixed that with this release.
Full Changelog: v5.1.1...v5.2.0
This patch release fixes behavior of resultEqualityCheck in weakMapMemoize , fixes the case of lruMemoize being given a maxSize less than 1, and tweak
This patch release fixes behavior of resultEqualityCheck in weakMapMemoize, fixes the case of lruMemoize being given a maxSize less than 1, and tweaks the internal implementation of lruMemoize. (We've also updated our general build tooling.)
Previously, providing the resultEqualityCheck option to weakMapMemoize resulted in it being called with empty objects as part of the initialization / dev check process. That could be an issue if your comparison function expected different values. We've updated the logic to avoid that, as well as improving a couple other perf aspects.
Previously, passing a maxSize < 1 to lruMemoize would result in it creating a larger cache. That's now fixed.
lruMemoize now uses a symbol for its NOT_FOUND value instead of a string.
lruMemoize correctly memoizes when maxSize is set to a number less than 1 by @aryaemami59 in #698resultEqualityCheck behavior in weakMapMemoize by @aryaemami59 in #699Full Changelog: v5.1.0...v5.1.1
Deprecates the TypedStructuredSelectorCreator type introduced in 5.0
This minor release:
createSelector.withTypes<RootState>() and createStructuredSelector.withTypes<RootState>() APITypedStructuredSelectorCreator type introduced in 5.0identityFunctionCheck by only running if the output selector is passed one argumentweakMapMemoize's resultEqualityCheck when used with a primitive result.withTypesMost commonly, selectors will accept the root state of a Redux store as their first argument. withTypes allows you to specify what that first argument will be ahead of creating the selector, meaning it doesn't have to be specified.
// previously
export const selectPostById = createSelector(
[
(state: RootState) => state.posts.entities,
(state: RootState, id: number) => id,
],
(entities, id) => entities[id],
);
// now
export const createAppSelector = createSelector.withTypes<RootState>();
export const selectPostById = createAppSelector(
[(state) => state.posts.entities, (state, id: number) => id],
(entities, id) => entities[id],
);Due to a Typescript issue, inference of the output selector's parameters only works with withTypes when using an array of input selectors.
If using the variadic version, you can either wrap your input selectors in an array instance (as above), or annotate the parameters manually.
export const createAppSelector = createSelector.withTypes<RootState>();
export const selectPostById = createAppSelector(
(state) => state.posts.entities,
(state, id: number) => id,
// parameters cannot be inferred, so need annotating
(entities: Record<number, Post>, id: number) => entities[id],
);identityFunctionCheck false positives by @Methuselah96 in #660_lastResult.deref is not a function (it is undefined) in React Native and Expo applications by @aryaemami59 in #671createSelector via createSelector.withTypes<RootState>() method by @aryaemami59 in #673TypedStructuredSelectorCreator by @aryaemami59 in #667createStructuredSelector via createStructuredSelector.ts.withTypes<RootState>() method by @aryaemami59 in #678vitest to v1 by @aryaemami59 in #668Full Changelog: v5.0.1...v5.1.0
This release has breaking changes. (note: this points to v5.0.1, which contains a hotfix that was released prior to the announcement.)
This major release:
createSelector to use a new weakMapMemoize method as the default memoizerdefaultMemoize method to lruMemoizecreateSelectorThis release has breaking changes. (note: this points to v5.0.1, which contains a hotfix that was released prior to the announcement.)
This release is part of a wave of major versions of all the Redux packages: Redux Toolkit 2.0, Redux core 5.0, React-Redux 9.0, Reselect 5.0, and Redux Thunk 3.0.
For full details on all of the breaking changes and other significant changes to all of those packages, see the "Migrating to RTK 2.0 and Redux 5.0" migration guide in the Redux docs.
We have a new docs site! The Reselect docs are now at https://reselect.js.org.
[!NOTE] The Redux core, Reselect, and Redux Thunk packages are included as part of Redux Toolkit, and RTK users do not need to manually upgrade them - you'll get them as part of the upgrade to RTK 2.0. (If you're not using Redux Toolkit yet, please start migrating your existing legacy Redux code to use Redux Toolkit today!)
# RTK
npm install @reduxjs/toolkit
yarn add @reduxjs/toolkit
# Standalone
npm install reselect
yarn add reselect
createSelector Uses weakMapMemoize By DefaultReselect's createSelector originally only had one memoization function, which has originally called defaultMemoize (and per below, is now renamed to lruMemoize). It's always used a customizable comparison method to compare each argument. Over time, we added more functionality, particularly in v4.1.0 where lruMemoize gained options for {memoize, maxSize, resultEqualityCheck}.
However, lruMemoize has limitations. The biggest one is that the default cache size is 1. This makes selector instances hard to reuse in scenarios like list items, which might call selectSomeValue(state, props.id), and thus never actually memoize due to changing arguments. There are workarounds, but they're cumbersome - using createSelectorCreator to create a customized createSelector function with a different memoization implementation, creating unique selector instances per component, or setting a fixed maxSize.
For 5.0, we added a new weakMapMemoize memoization function, which takes a different approach (as originally implemented in the React codebase). It uses an internal tree of cache nodes rather than a single value or a list of values. This gives weakMapMemoize an effectively infinite cache size!
We've done a fair amount of testing, and weakMapMemoize both performs faster and has more frequent cache hits than lruMemoize.
Given that, we've made the switch so that createSelector uses weakMapMemoize by default! This should result in better performance for Redux and React apps that use Reselect.
This is hopefully a mostly non-breaking change at the code level, and an overall improvement at the behavior level.
This is a breaking change. weakMapMemoize does not have an equalityCheck option or allow customizing the comparison behavior - it's entirely based on reference comparisons, since it uses WeakMap/Map internally. It also does not have a maxSize option, but does have resultEqualityCheck.
If you need to customize the overall equality comparison behavior, import and pass lruMemoize as the memoize and argsMemoize option!
Also, note that an "infinite cache size" from one point of view can be considered a "memory leak" for another point of view. The use of WeakMaps should mean that in most cases values do get garbage collected when the rest of the app no longer needs those, but there may be some scenarios with use of primitive keys that could lead to potential leaks. If this looks like it's happening for you, please compare behavior with lruMemoize instead, and file an issue report so we can investigate.
createSelector Memoization OptionsOriginally, the only way to customize createSelector's behavior (such as using an alternate memoization function) was to first create a customized version via createSelectorCreator(memoizerFunction, memoizerOptions). This was typically used for creating use cases like deep equality comparisons with _.equal instead of shallow equality, as well as alternate memoizers that had a notion of cache size.
With Reselect 4.1.0, we added the ability to pass memoizer options directly to createSelector, and also updated defaultMemoize to accept several options such as a max cache size. This meant that you could call createSelector(...inputFns, outputFn, {memoizeOptions: {maxSize: 100}}), but you couldn't change the memoizer _function_ being used directly - that still required use of createSelectorCreator`.
Additionally, Reselect internally uses the provided memoizer function twice internally: once on the overall arguments passed to selectSomeValue(a, b, c), and a second time on the values extracted by the input functions such as state => state.a. There have been multiple issues over the years where users wanted to provide separate memoization functions for the arguments vs the extracted values, such as a reference equality check for the arguments and a shallow check for the extracted values.
With this release, you can now pass alternate memoizer functions directly to createSelector, and both createSelector and createSelectorCreator accept separate options for memoize and argsMemoize (along with any options for those):
const selectTodoIds = createSelector(
(state: TodoState) => state.todos,
todos => todos.map(({ id }) => id),
{
memoize: defaultMemoize,
memoizeOptions: {
resultEqualityCheck: (a, b) => a === b
}
argsMemoize: microMemoize,
argsMemoizeOptions: {
isEqual: (a, b) => a === b
},
}
)
This should mostly eliminate the need to use createSelectorCreator for customization. (You can still use it for encapsulation / reuse if you want to create many selectors with the same customization options.)
The biggest theme of the Redux v5 and RTK 2.0 releases is trying to get "true" ESM package publishing compatibility in place, while still supporting CJS in the published package.
The primary build artifact is now an ESM file, dist/reselect.mjs. Most build tools should pick this up. There's also a CJS artifact, and a second copy of the ESM file named reselect.legacy-esm.js to support Webpack 4 (which does not recognize the exports field in package.json). Additionally, all of the build artifacts now live under ./dist/ in the published package.
We now publish modern JS syntax targeting ES2020, including optional chaining, object spread, and other modern syntax. If you need to
We're now building the package using https://github.com/egoist/tsup. We also now include sourcemaps for the ESM and CJS artifacts.
Redux has always shipped with UMD build artifacts. These are primarily meant for direct import as script tags, such as in a CodePen or a no-bundler build environment.
We've dropped those build artifacts from the published package, on the grounds that the use cases seem pretty rare today.
There's now a reselect.browser.mjs file in the package that can be loaded from a CDN like Unpkg.
If you have strong use cases for us continuing to include UMD build artifacts, please let us know!
createSelector now does checks in development mode for common mistakes, like input selectors that always return new references, or result functions that immediately return their argument. These checks can be customized at selector creation or globally.
This is important, as an input selector returning a materially different result with the same parameters means that the output selector will never memoize correctly and be run unnecessarily, thus (potentially) creating a new result and causing rerenders.
const addNumbers = createSelector(
// this input selector will always return a new reference when run
// so cache will never be used
(a, b) => ({ a, b }),
({ a, b }) => ({ total: a + b })
)
// instead, you should have an input selector for each stable piece of data
const addNumbersStable = createSelector(
(a, b) => a,
(a, b) => b,
(a, b) => ({
total: a + b,
})
)
This is done the first time the selector is called, unless configured otherwise. See the Reselect docs on dev-mode checks for more details.
We've dropped support for TS 4.6 and earlier, and our support matrix is now TS 4.7+.
The ParametricSelector and OutputParametricSelector types have been removed. Use Selector and OutputSelector instead.
The TS types have been updated to provide a better visual hover preview representation of a selector. It should now actually be previewed as "a function with attached fields", like:
const selectTodos: ((state: {
todos: {
id: number;
title: string;
description: string;
completed: boolean;
}[];
}) => number[]) & {
clearCache: () => void;
resultsCount: () => number;
resetResultsCount: () => void;
} & {
lastResult: () => number[];
recomputations: () => number;
resetRecomputations: () => void;
dependencyRecomputations: () => number;
resetDependencyRecomputations: () => void;
// snip additional attached functions
}
Selectors now have a dependencyRecomputions method that returns how many times the dependency memoizer recalculated, and a resetDependencyRecomputations method that resets that value.
Similarly, both weakMapMemoize and lruMemoize now have resultsCount and resetResultsCount methods that count how many times they actually calculated new values. This should be equal to the number of outer recomputations, unless you have passed in resultEqualityCheck as an option, in which case it only counts times a new actual reference was returned.
Huge thanks to @aryaemami59 for some incredibly comprehensive efforts reworking the internals of createSelector, our TS types, and the codebase structure in order to make all these changes possible!
createSelector. by @aryaemami59 in https://github.com/reduxjs/reselect/pull/626unstable_autotrackMemoize and bump Vitest version by @markerikson in https://github.com/reduxjs/reselect/pull/631resetRecomputations and resetDependencyRecomputations behavior to their types by @aryaemami59 in https://github.com/reduxjs/reselect/pull/646resultEqualityCheck to weakMapMemoize by @markerikson in https://github.com/reduxjs/reselect/pull/647EqualityFn slightly more type-safe. by @aryaemami59 in https://github.com/reduxjs/reselect/pull/651x => x. by @aryaemami59 in https://github.com/reduxjs/reselect/pull/645defaultMemoize to lruMemoize by @aryaemami59 in https://github.com/reduxjs/reselect/pull/654Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.8...v5.0.1
This release has breaking changes . (note: this points to v5.0.1, which contains a hotfix that was released prior to the announcement.)
This major release:
createSelector to use a new weakMapMemoize method as the default memoizerdefaultMemoize method to lruMemoizecreateSelectorThis release has breaking changes. (note: this points to v5.0.1, which contains a hotfix that was released prior to the announcement.)
This release is part of a wave of major versions of all the Redux packages: Redux Toolkit 2.0, Redux core 5.0, React-Redux 9.0, Reselect 5.0, and Redux Thunk 3.0.
For full details on all of the breaking changes and other significant changes to all of those packages, see the "Migrating to RTK 2.0 and Redux 5.0" migration guide in the Redux docs.
We have a new docs site! The Reselect docs are now at https://reselect.js.org.
Note
The Redux core, Reselect, and Redux Thunk packages are included as part of Redux Toolkit, and RTK users do not need to manually upgrade them - you'll get them as part of the upgrade to RTK 2.0. (If you're not using Redux Toolkit yet, please start migrating your existing legacy Redux code to use Redux Toolkit today!)
# RTK
npm install @reduxjs/toolkit
yarn add @reduxjs/toolkit
# Standalone
npm install reselect
yarn add reselectcreateSelector Uses weakMapMemoize By DefaultReselect's createSelector originally only had one memoization function, which has originally called defaultMemoize (and per below, is now renamed to lruMemoize). It's always used a customizable comparison method to compare each argument. Over time, we added more functionality, particularly in v4.1.0 where lruMemoize gained options for {memoize, maxSize, resultEqualityCheck}.
However, lruMemoize has limitations. The biggest one is that the default cache size is 1. This makes selector instances hard to reuse in scenarios like list items, which might call selectSomeValue(state, props.id), and thus never actually memoize due to changing arguments. There are workarounds, but they're cumbersome - using createSelectorCreator to create a customized createSelector function with a different memoization implementation, creating unique selector instances per component, or setting a fixed maxSize.
For 5.0, we added a new weakMapMemoize memoization function, which takes a different approach (as originally implemented in the React codebase). It uses an internal tree of cache nodes rather than a single value or a list of values. This gives weakMapMemoize an effectively infinite cache size!
We've done a fair amount of testing, and weakMapMemoize both performs faster and has more frequent cache hits than lruMemoize.
Given that, we've made the switch so that createSelector uses weakMapMemoize by default! This should result in better performance for Redux and React apps that use Reselect.
This is hopefully a mostly non-breaking change at the code level, and an overall improvement at the behavior level.
This is a breaking change. weakMapMemoize does not have an equalityCheck option or allow customizing the comparison behavior - it's entirely based on reference comparisons, since it uses WeakMap/Map internally. It also does not have a maxSize option, but does have resultEqualityCheck.
If you need to customize the overall equality comparison behavior, import and pass lruMemoize as the memoize and argsMemoize option!
Also, note that an "infinite cache size" from one point of view can be considered a "memory leak" for another point of view. The use of WeakMaps should mean that in most cases values do get garbage collected when the rest of the app no longer needs those, but there may be some scenarios with use of primitive keys that could lead to potential leaks. If this looks like it's happening for you, please compare behavior with lruMemoize instead, and file an issue report so we can investigate.
createSelector Memoization OptionsOriginally, the only way to customize createSelector's behavior (such as using an alternate memoization function) was to first create a customized version via createSelectorCreator(memoizerFunction, memoizerOptions). This was typically used for creating use cases like deep equality comparisons with _.equal instead of shallow equality, as well as alternate memoizers that had a notion of cache size.
With Reselect 4.1.0, we added the ability to pass memoizer options directly to createSelector, and also updated defaultMemoize to accept several options such as a max cache size. This meant that you could call createSelector(...inputFns, outputFn, {memoizeOptions: {maxSize: 100}}), but you couldn't change the memoizer _function_ being used directly - that still required use of createSelectorCreator`.
Additionally, Reselect internally uses the provided memoizer function twice internally: once on the overall arguments passed to selectSomeValue(a, b, c), and a second time on the values extracted by the input functions such as state => state.a. There have been multiple issues over the years where users wanted to provide separate memoization functions for the arguments vs the extracted values, such as a reference equality check for the arguments and a shallow check for the extracted values.
With this release, you can now pass alternate memoizer functions directly to createSelector, and both createSelector and createSelectorCreator accept separate options for memoize and argsMemoize (along with any options for those):
const selectTodoIds = createSelector(
(state: TodoState) => state.todos,
todos => todos.map(({ id }) => id),
{
memoize: defaultMemoize,
memoizeOptions: {
resultEqualityCheck: (a, b) => a === b
}
argsMemoize: microMemoize,
argsMemoizeOptions: {
isEqual: (a, b) => a === b
},
}
)This should mostly eliminate the need to use createSelectorCreator for customization. (You can still use it for encapsulation / reuse if you want to create many selectors with the same customization options.)
The biggest theme of the Redux v5 and RTK 2.0 releases is trying to get "true" ESM package publishing compatibility in place, while still supporting CJS in the published package.
The primary build artifact is now an ESM file, dist/reselect.mjs. Most build tools should pick this up. There's also a CJS artifact, and a second copy of the ESM file named reselect.legacy-esm.js to support Webpack 4 (which does not recognize the exports field in package.json). Additionally, all of the build artifacts now live under ./dist/ in the published package.
We now publish modern JS syntax targeting ES2020, including optional chaining, object spread, and other modern syntax. If you need to
We're now building the package using https://github.com/egoist/tsup. We also now include sourcemaps for the ESM and CJS artifacts.
Redux has always shipped with UMD build artifacts. These are primarily meant for direct import as script tags, such as in a CodePen or a no-bundler build environment.
We've dropped those build artifacts from the published package, on the grounds that the use cases seem pretty rare today.
There's now a reselect.browser.mjs file in the package that can be loaded from a CDN like Unpkg.
If you have strong use cases for us continuing to include UMD build artifacts, please let us know!
createSelector now does checks in development mode for common mistakes, like input selectors that always return new references, or result functions that immediately return their argument. These checks can be customized at selector creation or globally.
This is important, as an input selector returning a materially different result with the same parameters means that the output selector will never memoize correctly and be run unnecessarily, thus (potentially) creating a new result and causing rerenders.
const addNumbers = createSelector(
// this input selector will always return a new reference when run
// so cache will never be used
(a, b) => ({ a, b }),
({ a, b }) => ({ total: a + b })
)
// instead, you should have an input selector for each stable piece of data
const addNumbersStable = createSelector(
(a, b) => a,
(a, b) => b,
(a, b) => ({
total: a + b,
})
)This is done the first time the selector is called, unless configured otherwise. See the Reselect docs on dev-mode checks for more details.
We've dropped support for TS 4.6 and earlier, and our support matrix is now TS 4.7+.
The ParametricSelector and OutputParametricSelector types have been removed. Use Selector and OutputSelector instead.
The TS types have been updated to provide a better visual hover preview representation of a selector. It should now actually be previewed as "a function with attached fields", like:
const selectTodos: ((state: {
todos: {
id: number;
title: string;
description: string;
completed: boolean;
}[];
}) => number[]) & {
clearCache: () => void;
resultsCount: () => number;
resetResultsCount: () => void;
} & {
lastResult: () => number[];
recomputations: () => number;
resetRecomputations: () => void;
dependencyRecomputations: () => number;
resetDependencyRecomputations: () => void;
// snip additional attached functions
} Selectors now have a dependencyRecomputions method that returns how many times the dependency memoizer recalculated, and a resetDependencyRecomputations method that resets that value.
Similarly, both weakMapMemoize and lruMemoize now have resultsCount and resetResultsCount methods that count how many times they actually calculated new values. This should be equal to the number of outer recomputations, unless you have passed in resultEqualityCheck as an option, in which case it only counts times a new actual reference was returned.
Huge thanks to @aryaemami59 for some incredibly comprehensive efforts reworking the internals of createSelector, our TS types, and the codebase structure in order to make all these changes possible!
createSelector. by @aryaemami59 in #626unstable_autotrackMemoize and bump Vitest version by @markerikson in #631resetRecomputations and resetDependencyRecomputations behavior to their types by @aryaemami59 in #646resultEqualityCheck to weakMapMemoize by @markerikson in #647EqualityFn slightly more type-safe. by @aryaemami59 in #651x => x. by @aryaemami59 in #645defaultMemoize to lruMemoize by @aryaemami59 in #654Full Changelog: v4.1.8...v5.0.1
…to help track down details. This release has breaking changes .
This release candidate renames the original defaultMemoize function to lruMemoize, and improves dev check warnings to include a stack trace to help track down details. This release has breaking changes.
See the preview Redux Toolkit 2.0 + Redux core 5.0 Migration Guide for an overview of breaking changes in RTK 2.0 and Redux core.
npm install reselect@next
yarn add reselect@nextdefaultMemoize to lruMemoizeReselect's createSelector originally only had one memoization function, which has always called defaultMemoize. In the previous v5.0.0-rc.0 release, we switched createSelector to use the new weakMapMemoize function as the default.
That meant that defaultMemoize was now badly named, because it isn't the default any more.
Given that, we've renamed defaultMemoize to lruMemoize, to better describe what it does.
This should not affect most Reselect users, since few apps actually customize selector setup. For those that do have references to defaultMemoize in the codebase, replace those with lruMemoize.
defaultMemoize to lruMemoize by @aryaemami59 in #654Full Changelog: v5.0.0-rc.0...v5.0.0-rc.1
…x , and makes some final types tweaks. This has breaking changes .
This release candidate switches createSelector to use weakMapMemoize as the default memoization method, adds a resultEqualityCheck option to weakMapMemoize, reworks the dev mode check setup and adds a dev-mode check for result functions that look like x => x, and makes some final types tweaks. This has breaking changes.
See the preview Redux Toolkit 2.0 + Redux core 5.0 Migration Guide for an overview of breaking changes in RTK 2.0 and Redux core.
npm install reselect@next
yarn add reselect@nextcreateSelector Uses weakMapMemoize By DefaultReselect's createSelector originally only had one memoization function, which has always called defaultMemoize. It's always used a customizable comparison method to compare each argument. Over time, we added more functionality, particularly in v4.1.0 where defaultMemoize gained options for {memoize, maxSize, resultEqualityCheck}.
However, defaultMemoize has limitations. The biggest one is that the default cache size is 1. This makes selector instances hard to reuse in scenarios like list items, which might call selectSomeValue(state, props.id), and thus never actually memoize due to changing arguments. There are workarounds, but they're cumbersome - using createSelectorCreator to create a customized createSelector function with a different memoization implementation, creating unique selector instances per component, or setting a fixed maxSize.
For 5.0, we added a new weakMapMemoize memoization function, which takes a different approach. It uses an internal tree of cache nodes rather than a single value or a list of values. This gives weakMapMemoize an effectively infinite cache size!
We've done a fair amount of testing, and weakMapMemoize both performs faster and has more frequent cache hits than defaultMemoize.
Given that, we've made the switch so that createSelector uses weakMapMemoize by default! This should result in better performance for Redux and React apps that use Reselect.
This is hopefully a mostly non-breaking change at the code level, and an overall improvement at the behavior level.
This is a breaking change. weakMapMemoize does not have an equalityCheck option or allow customizing the comparison behavior - it's entirely based on reference comparisons, since it uses WeakMap/Map internally. It also does not have a maxSize option, but does have resultEqualityCheck.
If you need to customize the overall equality comparison behavior, import and pass defaultMemoize as the memoize and argsMemoize option!
Also, since defaultMemoize is no longer the actual "default" memoization function, we are considering a potential rename of defaultMemoize to something like lruMemoize to clarify the naming.
Earlier, we added an inputStabilityCheck that checked for input selectors that accidentally return new references, with a globally exported override method.
In this release, we've added an additional dev mode check that looks for result functions that look like x => x - in other words, passing the input function result straight through. This is almost always a logic error. Either the input selectors are doing too much work, or the selector is just performing a straight lookup with no derived values and it should be a plain function instead of memoized.
Both checks are customizable on a per-selector-instance basis, and also controllable at a global level:
setGlobalDevModeChecks({
inputStabilityCheck: 'always',
identityFunctionCheck: 'never'
})
createSelector(
[input1, input2],
resultFn,
{devModeChecks: {identityFunctionCheck: 'always'}}
)resetRecomputations and resetDependencyRecomputations behavior to their types by @aryaemami59 in #646resultEqualityCheck to weakMapMemoize by @markerikson in #647EqualityFn slightly more type-safe. by @aryaemami59 in #651x => x. by @aryaemami59 in #645Full Changelog: v5.0.0-beta.1...v5.0.0-rc.0
See the preview Redux Toolkit 2.0 + Redux core 5.0 Migration Guide for an overview of breaking changes in RTK 2.0 and Redux core.
This beta release improves the TS types so that hover previews of generated selectors have a much more readable format.
Note that we hope to release Redux Toolkit 2.0, Redux core 5.0, and React-Redux 9.0 by the start of December! (If we don't hit that, we'll aim for January, after the holidays.)
See the preview Redux Toolkit 2.0 + Redux core 5.0 Migration Guide for an overview of breaking changes in RTK 2.0 and Redux core.
npm install reselect@beta
yarn add reselect@betaFull Changelog: v5.0.0-beta.0...v5.0.0-beta.1
This beta release updates createSelector to accept additional memoization functions and memoizer options directly (without needing to use createSelect
This beta release updates createSelector to accept additional memoization functions and memoizer options directly (without needing to use createSelectorCreator first), renames an experimental memoizer to unstable_autotrackMemoizer for clarity, and drops support for TS 4.6 and earlier.
npm i reselect@beta
yarn add reselect@betaThis is part of the in-progress Redux Toolkit 2.0 beta work, and Reselect 5.0 will be included with Redux Toolkit 2.0 when it is released. Please try out RTK 2.0 beta and give us feedback!
createSelector Memoization OptionsOriginally, the only way to customize createSelector's behavior (such as using an alternate memoization function) was to first create a customized version via createSelectorCreator(memoizerFunction, memoizerOptions). This was typically used for creating use cases like deep equality comparisons with _.equal instead of shallow equality, as well as alternate memoizers that had a notion of cache size.
With Reselect 4.1.0, we added the ability to pass memoizer options directly to createSelector, and also updated defaultMemoize to accept several options such as a max cache size. This meant that you could call createSelector(...inputFns, outputFn, {memoizeOptions: {maxSize: 100}}), but you couldn't change the memoizer _function_ being used directly - that still required use of createSelectorCreator`.
Additionally, Reselect internally uses the provided memoizer function twice internally: once on the overall arguments passed to selectSomeValue(a, b, c), and a second time on the values extracted by the input functions such as state => state.a. There have been multiple issues over the years where users wanted to provide separate memoization functions for the arguments vs the extracted values, such as a reference equality check for the arguments and a shallow check for the extracted values.
With this release, you can now pass alternate memoizer functions directly to createSelector, and both createSelector and createSelectorCreator accept separate options for memoize and argsMemoize (along with any options for those):
const selectTodoIds = createSelector(
(state: TodoState) => state.todos,
todos => todos.map(({ id }) => id),
{
memoize: defaultMemoize,
memoizeOptions: {
resultEqualityCheck: (a, b) => a === b
}
argsMemoize: microMemoize,
argsMemoizeOptions: {
isEqual: (a, b) => a === b
},
}
)This should mostly eliminate the need to use createSelectorCreator for customization. (You can still use it for encapsulation / reuse if you want to create many selectors with the same customization options.)
Thanks to @aryaemami59 for some incredibly comprehensive efforts reworking the internals of createSelector, our TS types, and the codebase structure in order to make this possible!
We've dropped support for TS 4.6 and earlier. Our current TS support matrix is TS 4.7+.
The experimental autotrackMemoize function has been renamed to unstable_autotrackeMemoize. It will still be exported, but it needs significant further testing before we consider it ready.
createSelector. by @aryaemami59 in #626unstable_autotrackMemoize and bump Vitest version by @markerikson in #631Full Changelog: v5.0.0-alpha.2...v5.0.0-beta.0
This alpha release updates createSelector to run an extra one-time check for input selector stability in development builds, and updates the build art
This alpha release updates createSelector to run an extra one-time check for input selector stability in development builds, and updates the build artifacts to include sourcemaps and a browser-ready ESM production bundle.
Reselect uses two levels of memoization. The first level checks if any of the actual arguments have changed, and the second sees if the values extracted by the input functions have changed.
If an input function always returns a new reference, like (state) => ({a: state.a, b: state.b}) or (state) => state.items.map(), that will cause the selector to never memoize properly. This is a bug, similar conceptually to always returning a new reference in useSelector(), or always including a new reference in useEffect's dependencies array.
Since this is a common mistake, we've added a development mode check to catch this. By default, createSelector will now run try executing the memoization function twice during the first call to the selector. If the result appears to be different, it will log a warning with the arguments and the two different sets of extracted input values.
You can configure this behavior in two ways: by passing an inputStabilityCheck option directly to createSelector, or by importing the global setInputStabilityCheckEnabled() function:
type StabilityCheck = 'always' | 'once' | 'never'
const unstableInput = (a: number, b: number) => ({ a, b })
// Create a selector that double-checks the inputs every time it runs
const selector = createSelector(
unstableInput,
({ a, b }) => a + b,
{inputStabilityCheck: 'always'}
)
// Set all selectors to never double-check by default (unless given a specific option)
import { setInputStabilityCheckEnabled } from 'reselect'
setInputStabilityCheckEnabled('never')
We now include a reselect.browser.mjs ESM artifact has been compiled for production settings and is ready for use as an ES module in browsers.
We also have updated the reselect.legacy-esm.js artifact to transpile syntax for compat with Webpack 4.
Full Changelog: https://github.com/reduxjs/reselect/compare/v5.0.0-alpha.1...v5.0.0-alpha.2
This alpha release adds two experimental new memoizers with different capabilities and tradeoffs.
This alpha release adds two experimental new memoizers with different capabilities and tradeoffs.
npm i reselect@alpha
yarn add reselect@alpha
See the release notes for v5.0.0-alpha.0 for details on previous ESM/CJS build compat changes.
autotrack and weakmap MemoizersReselect has always allowed swapping out the function memoizer used inside of createSelector. Reselect's existing defaultMemoize memoizer is based on shallow equality checks for arguments. This is simple and fast, but also has limitations.
The most common limitation with defaultMemoize and shallow equality checks is that it can produce "false positive" recalculations. A classic example of this would be a selector that extracts an array of todo IDs:
const selectTodoIds = createSelector(
(state: RootState) => state.todos,
(todos) => todos.map(t => t.id)
)
If you dispatch a todoToggled() action that flips state.todos[3].completed, that will produce a new todo object at index 3 and a new todos array, because it's an immutable update. However, selectTodoIds will see that todos is a new reference and recalculate the result, even though none of the todo.id fields have changed. This creates a new IDs array that is shallow-equal to the last one. This is both a waste of computation time, and a new result reference that could cause a component to re-render even though the array hasn't conceptually changed.
createSelector has also always defaulted to a cache size of 1. With Reselect 4.1, we added a maxSize option to defaultMemoize, but this requires a known fixed cache size value at creation time. It's hard to estimate how many cache entries you might need in the future (will my list have 10 items? 100? 1000?).
This release includes two new experimental memoizers that have differing tradeoffs, with the goal of addressing these issues in different ways.
For now, both of these can be used by calling createSelectorCreator and generating a customized version of createSelector:
import { createSelectorCreator, autotrackMemoize, weakmapMemoize } from 'reselect'
const createSelectorAutotrack = createSelectorCreator(autotrackMemoize)
const createSelectorWeakmap = createSelectorCreator(weakmapMemoize)
In future 5.0-alpha releases, we'd like to investigate passing these directly to createSelector() calls.
autotrackMemoizeautotrackMemoize uses an "auto-tracking" approach inspired by the work of the Ember Glimmer team. It uses a Proxy to wrap arguments and track accesses to nested fields in your selector on first read. Later, when the selector is called with new arguments, it identifies which accessed fields have changed and only recalculates the result if one or more of those accessed fields have changed.
This allows it to be more precise than the shallow equality checks in defaultMemoize. In fact, with that exact same selectTodoIds code above, a selector that uses autotrackMemoize will not recalculate if you flip a todo.completed field, because it can see that you only accessed the todo.id fields.
This memoizer is directly based on the code and concepts from these articles and examples:
autotrackMemoizedefaultMemoize, because it has to do more work. (How much slower is dependent on the number of accessed fields in a selector, number of calls, frequency of input changes, etc)createSelector(state => state.todos, todos => todos) that just immediately returns the extracted value will never update, because it doesn't see any field accesses to check. (You shouldn't write selectors like that to begin with :) But we've seen them in the wild.)defaultMemoize will, which may also result in fewer component re-rendersautotrackMemoizeautotrackMemoize is likely best used for cases where you need to access specific nested fields in data, and avoid recalculating if other fields in the same data objects are immutably updated.
weakmapMemoizedefaultMemoize 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 functions). 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.
This memoizer is directly based on code from the React codebase:
weakmapMemoizedefaultMemoize, although likely a fraction slowerWeakMapsweakmapMemoizeThis 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, props.category)).
defaultMemoizeBack in PR #297, the outer argument memoization was changed to use the same provided memoization function as the inner extracted values memoization. For performance reasons, we've flipped this back to use defaultMemoize and shallow equality checks for the outer argument memoization. In most cases this should have no change at all for end users, because the memoizer is rarely overridden anyway.
We'd like to investigate allowing customization of both arguments and extracted values memoizers in a later 5.0-alpha release
Full Changelog: https://github.com/reduxjs/reselect/compare/v5.0.0-alpha.0...v5.0.0-alpha.1
This alpha release updates Reselect's build tooling, and updates the packaging for proper ESM compability.
This alpha release updates Reselect's build tooling, and updates the packaging for proper ESM compability.
This accompanies the Redux Toolkit 2.0 alphas, currently in progress.
npm i reselect@alpha
yarn add reselect@alpha
The biggest theme of the Redux v5 and RTK 2.0 releases is trying to get "true" ESM package publishing compatibility in place, while still supporting CJS in the published package.
Earlier RTK alphas made changes to the package.json contents and published build artifacts in an attempt to get ESM+CJS compat working correctly, but those alphas had several varying compat issues.
We've set up a battery of example applications in the RTK repo that use a variety of build tools (currently CRA4, CRA5, Next 13, and Vite, Node CJS mode, and Node ESM mode), to verify that Redux and Redux Toolkit compile, import, and run correctly with both TS and various bundlers. We've also set up a check using a custom CLI wrapper around https://arethetypeswrong.github.io to check for potential packaging incompatibilities.
This release changes the names and contents of the published build artifacts, and the various exports/module/main fields in package.json to point to those.
The primary build artifact is now an ESM file, dist/reselect.mjs. Most build tools should pick this up. There's also a CJS artifact as well.
As of this release, we think we have ESM+CJS compat working correctly, but we ask that the community try out the alphas in your apps and let us know of any compat problems!
Note: The one known potential issue is that TypeScript's new
moduleResolution: "node16"mode may see a mismatch between the ESM artifacts and the TS typedefs when imported in a Node CJS environment, and [that may allow hypothetically-incorrect import usage. (See ongoing discussion in https://github.com/arethetypeswrong/arethetypeswrong.github.io/issues/21 .) In practice, we think that probably won't be a concern, and we'll do further investigation before a final release.
Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.8...v5.0.0-alpha.0
This release updates our build tooling, tweaks the MergeParameters type to better handle spread values, and fixes an erroneous .clearCache() method in
This release updates our build tooling, tweaks the MergeParameters type to better handle spread values, and fixes an erroneous .clearCache() method included on the resultFunc.
main by @markerikson in https://github.com/reduxjs/reselect/pull/606Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.7...v4.1.8
This release updates the TS types to work correctly with TS 4.9, which made a change that broke the existing MergeParameters type implementation. Happ
This release updates the TS types to work correctly with TS 4.9, which made a change that broke the existing MergeParameters type implementation. Happily, the TS team provided a better (and simpler!) MergeParameters implementation. Since that only works with TS 4.7+, we've reworked the internals to handle providing the old implementation to TS 4.2..4.6, and the new implementation to TS 4.7 and greater.
As a user, there should be no visible change - just update to 4.1.7.
Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.6...v4.1.7
This release updates the TS types to better handle cases with default parameters, or any/unknown types.
This release updates the TS types to better handle cases with default parameters, or any/unknown types.
Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.5...v4.1.6
This release updates the TS types to correctly infer selector parameters when input selectors have undefined or null as a parameter type or have optio
This release updates the TS types to correctly infer selector parameters when input selectors have undefined or null as a parameter type or have optional parameters, and exports the CreateSelectorFunction type to fix uses of createStructuredSelector.
(The types fixes feel like playing whack-a-mole, but they keep getting better!
Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.4...v4.1.5
This release has (you guessed it) more fixes to the TS types: a change to parameter merging that fixes breakage with selectors and RTK Query's API sta
This release has (you guessed it) more fixes to the TS types: a change to parameter merging that fixes breakage with selectors and RTK Query's API state, a simplification of the OutputSelectorFields type to improve selector variable readability, another update to parameter merging to flag nested never fields as compile errors, and a fix to createStructuredSelector parameters to resolve a lib compilation problem.
The parameter merging fixes in 4.1.3 tried to "unwrap/expand" the parameter types to make them more readable, such as showing intersected objects as {a, b, c} instead of {a} & {b} & {c}. This was done with a recursive expansion type. That turned out to break with the complex state types used by RTK Query. We've updated the type expansion to only be a single level instead, which fixes the compilation issue.
The OutputSelectorFields type previously took two generics: the Combiner function, and a Result type. This led to extra values being shown in hover previews for selectors. By inferring Result = ReturnType<Combiner>, we were able to drop the second generic and cut down on the amount of types shown in previews.
A user noted that intersected objects with top-level incompatible fields (like {a: string} & {a: number}) resulted in empty objects, but no compile error. We've updated the parameter merging to flag those as never and catch the problem at compile time. Deeper nested incompatible fields should already be caught by TS.
The previous fix to createStructuredSelector missed a step in the spreading process, which has now been fixed.
Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.3...v4.1.4
This release rewrites the TS type inference of input selector parameters for correctness, fixes inference of createStructuredSelector inputs, and fixe
This release rewrites the TS type inference of input selector parameters for correctness, fixes inference of createStructuredSelector inputs, and fixes an issue with the OutputSelectorFields type not being exported.
Reselect's types have always been extremely tricky, because it involves passing multiple input selectors with potentially heterogeneous, and then nested function composition of multiple selectors. Additionally, the input selectors can be passed as individual arguments or a single array of input selectors.
The 4.0.0 typedefs dealt with this by hand-writing dozens of overloads, which was absolutely impossible to maintain.
In 4.1, we took advantage of TS's improved abilities to infer array/tuple types to consolidate the typedefs.
One of the issues that happened as a result was that arguments at the same input parameter index were being "unioned" together, rather than "intersectioned". For example, in this complex selector:
const input1 = (
_: StateA,
{ testNumber }: { testNumber: number },
c: number,
d: string
) => testNumber
const input2 = (
_: StateA,
{ testString }: { testString: string },
c: number | string
) => testString
const input3 = (
_: StateA,
{ testBoolean }: { testBoolean: boolean },
c: number | string,
d: string
) => testBoolean
const input4 = (_: StateA, { testString2 }: { testString2: string }) =>
testString2
const testSelector = createSelector(
input1,
input2,
input3,
input4,
(testNumber, testString, testBoolean) => testNumber + testString
)
The second arg should end up as an object like {testNumber: number, testString: string, testBoolean: boolean, testString2: string}. However, it was ending up as four separate one-field objects. Similarly, the combination of number and number | string should be narrowed down to just number as an acceptable value.
We've rewritten the types to successfully accomplish that (although it took a lot of collective effort and headbanging to actually pull this off!) This should now give much more correct results when determining the final parameters that can be passed to a selector.
createStructuredSelector FixesSimilarly, createStructuredSelector wasn't always inferring its arguments properly. We were able to reuse the parameter inference work here as well.
OutputSelectorFields ExportedThe public OutputSelector type depended on an internal OutputSelectorFields type, but since OSF wasn't being exported, TS would throw errors when trying to generate declaration files that exported selectors. That is now public as well.
Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.2...v4.1.3
This release updates the TS types to avoid TypeScript recursion limitations and improve backwards compatibility, adds doc comments to most of the TS t
This release updates the TS types to avoid TypeScript recursion limitations and improve backwards compatibility, adds doc comments to most of the TS types and field declarations, and fixes a bug with the behavior of the resultEqualityCheck option in defaultMemoize.
We saw cases where composition of selectors past 8-9 levels of nesting would cause TS to fail with a "Type instantiation is excessively deep and possibly infinite" error.
We've updated the types to allow additional recursion up to about 15 levels of nested selectors. Hopefully this is enough for most usages :)
The OutputSelector generic arguments had been swapped during the rewrite for 4.1, which made it incompatible with other code that attempted to import and use that type. We've reverted the generic arguments to their previous order to fix compatibility.
defaultMemoize adds a .clearCache() field to its return value. While the real caching is done by the memoizedResultFunc function, the actual returned selector has also been run through the memoizer and thus also has a .clearCache() field attached, but that wasn't captured in the types. We've updated the types to reflect that.
We've also added doc comments to almost all of the internal types for clarity, as well as comments to the returned fields on selectors.
resultEqualityCheck BehaviorThe resultEqualityCheck option wasn't saving the result if there was a cache hit, which is now fixed.
Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.1...v4.1.2
This releases fixes several TS issues and one runtime issue that were reported with the release of 4.1.0.
This releases fixes several TS issues and one runtime issue that were reported with the release of 4.1.0.
All these reported issues should now be fixed:
createSelector calls with 12 or more input selectors were causing TS to fail with a "Type instantiation is excessively deep" error. After this update, createSelector should now support up to 29 input selectors before TS has type issues. (and if you've got more than 29 input selectors.... what are you doing? :) )(a: number) => 42, (b: string) => 123)OutputParametricSelector type, which is re-exported by Redux Toolkit, was inadvertently left out of the list of Reselect type exports during the rewrite and caused RTK builds to failSomeType | undefined were causing the entire selector to be typed as possibly returning undefinedThe previous internal cache logic had a couple of if (foundValue !== undefined) checks inside, but that broke cases where a selector intentionally wanted to return undefined as the actual result.
The cache logic has been updated to use an internal sentinel value as the NOT_FOUND result instead, allowing undefined to be correctly cached and returned.
GetStateFromSelectors by @phryneas in https://github.com/reduxjs/reselect/pull/529undefined by @markerikson in https://github.com/reduxjs/reselect/pull/532Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.0...v4.1.1
This long-overdue release updates defaultMemoize to accept new options for cache size > 1 and a result equality check, updates createSelector to accep
This long-overdue release updates defaultMemoize to accept new options for cache size > 1 and a result equality check, updates createSelector to accept an options object containing options for the provided memoize function, makes major improvements to the TypeScript types (targeting TS 4.2+), converts the codebase to TS, improves some error messages, and adds memoizedResultFunc and lastResult to the fields attached to the selector,
This should be a drop-in update - the only expected backwards compatibility issues are with incorrect or very outdated TypeScript usage patterns.
Update: see https://github.com/reduxjs/reselect/releases/tag/v4.1.1 for fixes to several TS and other issues that were reported with the 4.1.0 release
npm i reselect@latest
yarn add reselect@latest
defaultMemoize OptionsdefaultMemoize has always been fairly limited. Its signature was (func: Function, equalityCheck?: EqualityFn) => Function, and only ever had a cache size of 1. This has led to many annoyances and workarounds, typically involving calling createSelectorCreator() with a custom memoization function that has a larger cache size or more options for customizing comparisons.
We've updated defaultMemoize to allow cache sizes > 1, as well as customize comparisons of the newly generated result value to improve cache hits.
The signature for defaultMemoize is now:
interface DefaultMemoizeOptions {
equalityCheck?: EqualityFn
resultEqualityCheck?: EqualityFn
maxSize?: number
}
// defaultMemoize now supports a configurable cache size with LRU behavior,
// and optional comparison of the result value with existing values
export function defaultMemoize<F extends (...args: any[]) => any>(
func: F,
equalityCheckOrOptions?: EqualityFn | DefaultMemoizeOptions
): F
In other words, you can still pass equalityCheck as its one additional arg, or you may pass an object containing several possible options.
If the maxSize value is greater than 1, defaultMemoize will now use an LRU cache based on https://github.com/erikras/lru-memoize internally.
If resultEqualityCheck is provided, it will be used to compare the newly-generated value from func against all other values in the cache, in LRU order. If a cached value is found to be equal, that value will be returned. This addresses the common todos.map(todo => todo.id) use case, where a change to any field in any todo object creates a new todos array and thus causes the output to be recalculated, but the generated IDs array is still shallow-equal to the last result. You can now pass an equality function like shallowEqual as the resultEqualityCheck argument, and it will reuse the old IDs array instead.
createSelector OptionsPreviously, the only way to customize behavior of createSelector was to generate a customized version with createSelectorCreator. By far the most common use case was customizing the equalityCheck option used with defaultMemoize, or using a different memoizer entirely. This usually looked like:
const createShallowEqualSelector = createSelectorCreator(defaultMemoize, shallowEqual)
const createDeepEqualSelector = createSelectorCreator(defaultMemoize, _.isEqual)
const createCustomComparisonSelector = createSelector(_.memoize, hashFn)
createSelectorCreator also accepted additional positional parameters, and forwarded all of them to the provided memoize function, so defaultMemoize ultimately gets called internally as defaultMemoize(actualFunction, shallowEqual).
This added an annoying level of indirection to common customization use cases.
createSelector now accepts an options object as its last argument, after the output selector. Currently, that object only includes one field: memoizeOptions:
interface CreateSelectorOptions<MemoizeOptions extends unknown[]> {
memoizeOptions: MemoizeOptions[0] | MemoizeOptions
}
Similar to how createSelectorCreator accepts additional "options args" that get forwarded to the memoization function, the memoizeOptions field accepts an array of those "options args" as well. If provided, these override what was given to createSelectorCreator.
That means that you can now customize memoization behavior with direct options to createSelector. And, because defaultMemoize now accepts more options, you can directly customize defaultMemoize's behavior without using createSelectorCreator.
Additionally, because it's very common to only need to pass one options arg to the memoization function, memoizeOptions may also be just that first options arg by itself, without any array.
Example usages of this look like:
const createSelectorAcceptsArgsAsArray = createSelector(
(state: StateAB) => state.a,
(state: StateAB) => state.b,
(a, b) => a + b,
{
// Pass `equalityCheck`, the first options arg of `defaultMemoize`, in an array
memoizeOptions: [(a, b) => a === b]
}
)
const createSelectorFirstArgDirectly = createSelector(
(state: StateAB) => state.a,
(state: StateAB) => state.b,
(a, b) => a + b,
{
// Pass `equalityCheck`, the first options arg of `defaultMemoize`, directly
memoizeOptions: (a, b) => a === b
}
)
const defaultMemoizeAcceptsFirstArgAsObject = createSelector(
(state: StateAB) => state.a,
(state: StateAB) => state.b,
(a, b) => a + b,
{
// Pass `options`, the _alternate_ first arg of `defaultMemoize`, directly
memoizeOptions: {
equalityCheck: (a, b) => a === b,
maxSize: 10,
resultEqualityCheck: shallowEqual
}
}
)
// Can still create custom selectors by passing args to `createSelectorCreator`
const customSelectorCreatorMicroMemoize = createSelectorCreator(
microMemoize,
{
maxSize: 42
}
)
This should make it much easier to customize behavior.
All of this is fully TypeScript-typed, and the possible values for memoizeOptions should be fully inferred from the provided memoize function.
Additionally, defaultMemoize now supports clearing the cache inside a memoized function (regardless of cache size). The memoized function returned from defaultMemoize will now have a .clearCache() method attached that will clear the cache.
When using createSelector, this can be accessed using selector.memoizedResultFunc.clearCache().
The Reselect types were written several years ago and originally targeted TS 2.x versions. As a result, the typedefs requires dozens of overloads to handle varying numbers of arguments (see the legacy typedefs file for examples).
We've converted the codebase to be written in TypeScript, and as part of that process we've completely rewritten the TS typedefs to use modern TS syntax like mapped types. This drastically shrinks the size of the typedefs (from 1000 lines to about 115), and also improves the actual type inference overall. Assuming the input selectors are correctly and consistently typed, TS will now fully infer the return values of all input selectors, the arguments to the output selector, and the exact type of the memoized function.
The updated types do require use of TS 4.2+. We've attempted to keep the final public type names and usage the same, but there may also be some types breakage. We'd appreciate feedback on any meaningful breakage issues so we can make further tweaks if needed.
Given the intent of the improvements, that they're all type-only changes, the attempts to retain backwards compatibility, and TS's own versioning scheme, we're considering this to be a minor version change rather than a major.
In pre-release testing, the main issues we saw were:
state arg. Fix: explicitly add a type to state<A, B, C, D> generics from the createSelector() call.The legacy types are still included, and should automatically be used if you are using TS 4.1 and earlier. Note that the legacy types do not include the definitions for the new defaultMemoize options - you'll need to be on TS 4.2+ to use those with TS.
We've improved the error messages thrown when invalid selectors are provided.
Generated selectors now include selector.memoizedResultFunc and selector.lastResult for later access if needed.
The early alphas contained code from several outstanding PRs, pulled together:
memoize type fixes ( @micahbales )createStructuredSelector inference ( @oatkiller )Additional work included:
defaultMemoize to accept options (maxSize, equalityCheck, resultEqualityCheck) by @markerikson in https://github.com/reduxjs/reselect/pull/513clearCache method to defaultMemoize output functions by @markerikson in https://github.com/reduxjs/reselect/pull/519Full Changelog: https://github.com/reduxjs/reselect/compare/v4.0.0...v4.1.0
This release fixes an issue with the typesVersions package field so that TS 4.1 and earlier correctly pick up the legacy type definitions - no other c
This release fixes an issue with the typesVersions package field so that TS 4.1 and earlier correctly pick up the legacy type definitions - no other code changes.
npm i reselect@next
yarn add reselect@next
-Fix typesVersions syntax to work with TS 4.1 and earlier 4ebcc36
https://github.com/reduxjs/reselect/compare/v4.1.0-beta.1...v4.1.0-beta.2
This release fixes a couple test-related packages that were accidentally listed as dependencies instead of devDependencies, and adds the sideEffects f
This release fixes a couple test-related packages that were accidentally listed as dependencies instead of devDependencies, and adds the sideEffects flag to package.json in case it's useful.
There are no code changes from 4.1.0-beta.0: https://github.com/reduxjs/reselect/releases/tag/v4.1.0-beta.0
npm i reselect@next
yarn add reselect@next
Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.0-beta.0...v4.1.0-beta.1
This beta release updates defaultMemoize with the ability to clear cache for a memoized function, and updates the TS types of createSelector to correc
This beta release updates defaultMemoize with the ability to clear cache for a memoized function, and updates the TS types of createSelector to correctly infer the type of the function returned from the memoizer.
We would appreciate any feedback on the behavior of the new features and compatibility of the TS types, in preparation for a final 4.1.0 release.
npm i reselect@next
yarn add reselect@next
defaultMemoize Cache ClearingdefaultMemoize now supports clearing the cache inside a memoized function. The memoized function returned from defaultMemoize will now have a .clearCache() method attached that will clear the cache.
When using createSelector, this can be accessed using selector.memoizedResultFunc.clearCache().
createSelector TypeScript Return Type Inference ImprovementscreateSelector should now fully infer the type of the memoized function returned by the memoize parameter. This means that standard use of createSelector, which already has defaultMemoize built in, will also correctly infer the existence of selector.memoizedResultFunc.clearCache().
clearCache method to defaultMemoize output functions by @markerikson in https://github.com/reduxjs/reselect/pull/519Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.0-alpha.2...v4.1.0-beta.0
This alpha release updates defaultMemoize to accept new options for cache size > 1 and a result equality check, updates createSelector to accept an op
This alpha release updates defaultMemoize to accept new options for cache size > 1 and a result equality check, updates createSelector to accept an options object containing options for the provided memoize function, improves some error messages, and adds memoizedResultFunc and lastResult to the fields attached to the selector.
These changes enable major improvements in functionality for createSelector, and should resolve almost all the concerns and pain points experienced by our users.
Although marked as an alpha, the code should be stable and be ready to ship as 4.1.0 in the very near future pending feedback on any potential upgrade issues.
npm i reselect@next
yarn add reselect@next
defaultMemoize OptionsdefaultMemoize has always been fairly limited. Its signature was (func: Function, equalityCheck?: EqualityFn) => Function, and only ever had a cache size of 1. This has led to many annoyances and workarounds, typically involving calling createSelectorCreator() with a custom memoization function that has a larger cache size or more options for customizing comparisons.
We've updated defaultMemoize to allow cache sizes > 1, as well as customize comparisons of the newly generated result value to improve cache hits.
The signature for defaultMemoize is now:
interface DefaultMemoizeOptions {
equalityCheck?: EqualityFn
resultEqualityCheck?: EqualityFn
maxSize?: number
}
// defaultMemoize now supports a configurable cache size with LRU behavior,
// and optional comparison of the result value with existing values
export function defaultMemoize<F extends (...args: any[]) => any>(
func: F,
equalityCheckOrOptions?: EqualityFn | DefaultMemoizeOptions
): F
In other words, you can still pass equalityCheck as its one additional arg, or you may pass an object containing several possible options.
If the maxSize value is greater than 1, defaultMemoize will now use an LRU cache based on https://github.com/erikras/lru-memoize internally.
If resultEqualityCheck is provided, it will be used to compare the newly-generated value from func against all other values in the cache, in LRU order. If a cached value is found to be equal, that value will be returned. This addresses the common todos.map(todo => todo.id) use case, where a change to any field in any todo object creates a new todos array and thus causes the output to be recalculated, but the generated IDs array is still shallow-equal to the last result. You can now pass an equality function like shallowEqual as the resultEqualityCheck argument, and it will reuse the old IDs array instead.
createSelector OptionsPreviously, the only way to customize behavior of createSelector was to generate a customized version with createSelectorCreator. By far the most common use case was customizing the equalityCheck option used with defaultMemoize, or using a different memoizer entirely. This usually looked like:
const createShallowEqualSelector = createSelectorCreator(defaultMemoize, shallowEqual)
const createDeepEqualSelector = createSelectorCreator(defaultMemoize, _.isEqual)
const createCustomComparisonSelector = createSelector(_.memoize, hashFn)
createSelectorCreator also accepted additional positional parameters, and forwarded all of them to the provided memoize function, so defaultMemoize ultimately gets called internally as defaultMemoize(actualFunction, shallowEqual).
This added an annoying level of indirection to common customization use cases.
createSelector now accepts an options object as its last argument, after the output selector. Currently, that object only includes one field: memoizeOptions:
interface CreateSelectorOptions<MemoizeOptions extends unknown[]> {
memoizeOptions: MemoizeOptions[0] | MemoizeOptions
}
Similar to how createSelectorCreator accepts additional "options args" that get forwarded to the memoization function, the memoizeOptions field accepts an array of those "options args" as well. If provided, these override what was given to createSelectorCreator.
That means that you can now customize memoization behavior with direct options to createSelector. And, because defaultMemoize now accepts more options, you can directly customize defaultMemoize's behavior without using createSelectorCreator.
Additionally, because it's very common to only need to pass one options arg to the memoization function, memoizeOptions may also be just that first options arg by itself, without any array.
Example usages of this look like:
const createSelectorAcceptsArgsAsArray = createSelector(
(state: StateAB) => state.a,
(state: StateAB) => state.b,
(a, b) => a + b,
{
// Pass `equalityCheck`, the first options arg of `defaultMemoize`, in an array
memoizeOptions: [(a, b) => a === b]
}
)
const createSelectorFirstArgDirectly = createSelector(
(state: StateAB) => state.a,
(state: StateAB) => state.b,
(a, b) => a + b,
{
// Pass `equalityCheck`, the first options arg of `defaultMemoize`, directly
memoizeOptions: (a, b) => a === b
}
)
const defaultMemoizeAcceptsFirstArgAsObject = createSelector(
(state: StateAB) => state.a,
(state: StateAB) => state.b,
(a, b) => a + b,
{
// Pass `options`, the _alternate_ first arg of `defaultMemoize`, directly
memoizeOptions: {
equalityCheck: (a, b) => a === b,
maxSize: 10,
resultEqualityCheck: shallowEqual
}
}
)
// Can still create custom selectors by passing args to `createSelectorCreator`
const customSelectorCreatorMicroMemoize = createSelectorCreator(
microMemoize,
{
maxSize: 42
}
)
This should make it much easier to customize behavior.
All of this is fully TypeScript-typed, and the possible values for memoizeOptions should be fully inferred from the provided memoize function.
We've improved the error messages thrown when invalid selectors are provided.
Generated selectors now include selector.memoizedResultFunc and selector.lastResult for later access if needed.
defaultMemoize to accept options (maxSize, equalityCheck, resultEqualityCheck) by @markerikson in https://github.com/reduxjs/reselect/pull/513Full Changelog: https://github.com/reduxjs/reselect/compare/v4.1.0-alpha.1...v4.1.0-alpha.2
This alpha release migrates the Reselect source to TypeScript, and updates all associated build tooling. There are no further changes to runtime behav
This alpha release migrates the Reselect source to TypeScript, and updates all associated build tooling. There are no further changes to runtime behavior.
npm i reselect@next
yarn add reselect@next
We plan on tackling actual API improvements in upcoming alpha releases, such as possible new options for cache size and memoization behavior.
Following on from the rewrite of the TS typedefs in https://github.com/reduxjs/reselect/releases/tag/v4.1.0-alpha.0 , we've gone ahead and migrated the actual Reselect source to TS using those updated types. There were some additional tweaks needed to make this work (such as using interfaces rather than function overloads), but the types themselves should work exactly the same as alpha.0. All existing type tests pass, and we've confirmed that some existing TS+Redux apps still compile correctly if Reselect is upgraded to this build.
Along with that, the build tooling has been updated to properly compile TypeScript (based on the current build setup for React-Redux), and we've switched the test setup to use Jest instead of Mocha for consistency.
https://github.com/reduxjs/reselect/compare/v4.1.0-alpha.0...v4.1.0-alpha.1
This alpha preview release rewrites the TypeScript types to target TypeScript 4.2+, adds automatic type inference for createStructuredSelector, fixes
This alpha preview release rewrites the TypeScript types to target TypeScript 4.2+, adds automatic type inference for createStructuredSelector, fixes a longstanding bug with the equalityCheck argument to defaultMemoize and its usage with createSelectorCreator, and updates build tooling.
npm i reselect@next
yarn add reselect@next
This is the first release in several years, due to the original maintainer @ellbee dealing with other obligations. Thanks to him for all his hard work, and for giving additional maintainers access.
We have an open roadmap discussion asking for feedback on a potential Reselect v5 API design, and would appreciate additional input and ideas there.
The Reselect types were written several years ago and originally targeted TS 2.x versions. As a result, the typedefs requires dozens of overloads to handle varying numbers of arguments (see the legacy typedefs file for examples).
We've completely rewritten the TS typedefs to use modern TS syntax like mapped types. This drastically shrinks the size of the typedefs (from 1000 lines to about 115), and also improves the actual type inference overall.
The updated types do require use of TS 4.2+. We've attempted to keep the final public type names and usage the same, but there may also be some types breakage. We'd appreciate feedback on any meaningful breakage issues so we can make further tweaks if needed.
Given the intent of the improvements, that they're all type-only changes, the attempts to retain backwards compatibility, and TS's own versioning scheme, we're considering this to be a minor version change rather than a major.
The legacy types are still included, and should automatically be used if you are using TS 4.1 and earlier.
In some cases passing an equalityCheck function to defaultMemoize would not infer the right types for the (a, b) arguments, either when used by itself or as an argument to createSelectorCreator. Those types should now be inferred correctly.
As part of that work, the types had long declared that equalityCheck functions took index: number as a third parameter. That has not been true in the actual JS code since late 2016, but the types weren't updated to match the runtime behavior. That is now fixed.
A new overload of createSelectorCreator has been added that will infer the type of state for the overall selector if all input selectors have the state argument typed.
Reselect now (finally) uses Babel 7. We're using Github Actions for CI and running type tests against TS4.2+.
This alpha release is from a still-draft PR, #486, and contains code from:
memoize type fixes ( @micahbales )createStructuredSelector inference ( @oatkiller )Full Changelog: https://github.com/reduxjs/reselect/compare/v4.0.0...v4.1.0-alpha.0
Updated TypeScript typings (#274, #315) Exposed selector dependencies (#251) Use provided memoize function for selectors
Updated TypeScript typings (#274, #315) Exposed selector dependencies (#251) Use provided memoize function for selectors (#297)
Exposed selector dependencies (#251)
Use provided memoize function for selectors (#297)
Updated TypeScript typings (#274, #315)
Nothing published for this version
Fix selector type for using the right extension, see https://github.com/reactjs/reselect/pull/240
Fix selector type for using the right extension, see https://github.com/reactjs/reselect/pull/240
Performance improvements (thanks to @johnhaley81) Updated Typescript typings (thanks to everyone who helped)
Performance improvements (thanks to @johnhaley81) Updated Typescript typings (thanks to everyone who helped)
For performance reasons, a selector is now not recalculated if its input is equal by reference (===).
import { createSelector } from 'reselect';
const mySelector = createSelector(
state => state.values.filter(val => val < 5),
values => {
console.log('calling..')
return values.reduce((acc, val) => acc + val, 0)
}
)
var createSelector = require('./dist/reselect.js').createSelector;
const mySelector = createSelector(
state => state.values.filter(val => val < 5),
values => {
console.log('calling..')
return values.reduce((acc, val) => acc + val, 0)
}
)
var state1 = {values: [1,2,3,4,5,6,7,8,9]};
console.log(mySelector(state1));
state1.values = [3,4,5,6,7,8,9];
console.log(mySelector(state1));
var state2 = {values: [1,2,3,4,5,6,7,8,9]};
console.log(mySelector(state2));
var state3 = {values: [3,4,5,6,7]};
console.log(mySelector(state3));
calling..
10
calling..
7
calling..
10
calling..
7
calling..
10
10
calling..
10
calling..
7
Nothing published for this version
If there are no problems reported, I'll release 3.0.0 proper next week.
npm install -S reselect@rc
If there are no problems reported, I'll release 3.0.0 proper next week.
Performance improvements (thanks to @johnhaley81) Updated Typescript typings (thanks to everyone who helped)
Nothing published for this version
Improve performance of defaultMemoize when using custom equality check.
Improve performance of defaultMemoize when using custom equality check. (#170)
Reverts a Typescript change that was a breaking change. It will be reinstated in a major release.
Reverts a Typescript change that was a breaking change. It will be reinstated in a major release. (#145)
When a selector uses defaultMemoize, if an exception is thrown for a set of arguments then the selector should also throw when called again with those
When a selector uses defaultMemoize, if an exception is thrown for a set of arguments then the selector should also throw when called again with those arguments. (#144)
Include es directory in package.json
Include es directory in package.json (#117)
## New features Add jsnext build
Add jsnext build (#116)
## New features Add umd build
Add umd build (#112)
Add resultFunc property to selectors
Add resultFunc property to selectors (#92)
Remove test files from npm lib folder
Remove test files from npm lib folder
Add Typescript typing to package.json
Add Typescript typing to package.json (#94)
Add resetRecomputations method to selectors
Add resetRecomputations method to selectors (#90)
Fix bug (#78) in defaultMemoize which could cause the memoized value to be mistakenly returned for variadic functions.
Fix bug (#78) in defaultMemoize which could cause the memoized value to be mistakenly returned for variadic functions.
Fix bug (#78) in defaultMemoize which could cause the memoized value to be mistakenly returned for variadic functions.
Fix IE8 support by compiling in 'loose' mode
Fix IE8 support by compiling in 'loose' mode
Update NPM to have latest README and github links since move to rackt.
Update NPM to have latest README and github links since move to rackt.
Input selectors are now verified to be functions during selector creation. If verification fails an error is thrown, allowing for a useful stack trace
createSelector, createStructuredSelector, and custom selector creators check argument typesInput selectors are now verified to be functions during selector creation. If verification fails an error is thrown, allowing for a useful stack trace. (see #49)
There is a small chance that this could cause a breakage in existing code that contains a faulty selector that is never called.
createSelector, createStructuredSelector, and custom selector creators check argumentsInput selectors are now verified to be functions during selector creation. If verification fails an error is thrown, allowing for a useful stack trace
There is a small chance that this could cause a breaking change in code that contains a faulty selector that is never called.
createStructuredSelector is a convenience function that helps with a common pattern when using Reselect. The selector passed to a connect decorator of
createStructuredSelectorcreateStructuredSelector is a convenience function that helps with a common pattern when using Reselect. The selector passed to a connect decorator often just takes other selectors and maps them to keys in an object:
const mySelectorA = state => state.a;
const mySelectorB = state => state.b;
const structuredSelector = createSelector(
mySelectorA,
mySelectorB,
mySelectorC,
(a, b, c) => ({
a,
b,
c
})
);
createStructuredSelector takes an object whose properties are input-selectors and returns a structured selector. The structured selector returns an object with the same keys as the inputSelectors argument, but with the selectors replaced with their values.
const mySelectorA = state => state.a;
const mySelectorB = state => state.b;
const structuredSelector = createStructuredSelector({
x: mySelectorA,
y: mySelectorB
});
const result = structuredSelector({a: 1, b: 2}); // will produce {x: 1, y: 2}
If upgrading from 0.0.2, see the release notes for v1.0.0-alpha
If upgrading from 0.0.2, see the release notes for v1.0.0-alpha
/src directory included in npm package js:next field added to package.json
/src directory included in npm package js:next field added to package.json
createSelectorCreator takes a user specified memoize function instead of a custom valueEqualsFunc.
createSelectorCreator takes a user specified memoize function instead of a custom valueEqualsFunc.
import { isEqual } from 'lodash';
import { createSelectorCreator } from 'reselect';
const deepEqualsSelectorCreator = createSelectorCreator(isEqual);
import { isEqual } from 'lodash';
import { createSelectorCreator, defaultMemoize } from 'reselect';
const deepEqualsSelectorCreator = createSelectorCreator(
defaultMemoize,
isEqual
);
Selector creators can receive a variadic number of dependencies as well as an array of dependencies.
const selector = createSelector(
[state => state.a, state => state.b],
(a, b) => a * b
);
const selector = createSelector(
state => state.a,
state => state.b,
(a, b) => a * b
);
ownProps in SelectorSelector dependencies can receive a variadic number of parameters allowing a selector to receive ownProps passed from mapToProps in connect.
const selector = createSelector(
(state) => state.a,
(state, props) => state.b * props.c,
(_, props) => props.d,
(a, bc, d) => a + bc + d
);
import { createSelectorCreator } from 'reselect';
import memoize from 'lodash.memoize';
let called = 0;
const customSelectorCreator = createSelectorCreator(memoize, JSON.stringify);
const selector = customSelectorCreator(
state => state.a,
state => state.b,
(a, b) => {
called++;
return a + b;
}
);
assert.equal(selector({a: 1, b: 2}), 3);
assert.equal(selector({a: 1, b: 2}), 3);
assert.equal(called, 1);
assert.equal(selector({a: 2, b: 3}), 5);
assert.equal(called, 2);
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →