@reduxjs/toolkit
2.13.0 · 29 Sep 2026MIT45.8M / weekRepositoryHomepagenpmAtom feed
72 stable releases, newest first, by release line. Notes are quoted as the maintainers published them.
- 2.x · latest line
2.13.0
29 Sep 2026Additional publishing hardening
codegen: Migrate to oazapfts v7
combineSlices: Scope stateProxyMap per instance to prevent cross-instance proxy collisions
This feature release updates our build tooling to TSDown and PNPM, adds official TypeScript 7 support, and includes a long list of bugfixes across RTK Query,
createAsyncThunk,createEntityAdapter, andcombineSlices.Changelog
Build Tooling Updates
We've fully modernizing modernized our build tooling across all of the Redux repos. That included switching from Yarn to PNPM, ESLint to Oxlint, Prettier to Oxfmt, and TSUp to TSDown.
The part that matters for users is that we now build the package with TSDown instead of
tsup. The package layout,exportsdefinitions, and exported APIs are all unchanged from 2.12. We've checked the new build withattw, and verified that CJS and ESM entry points load in both dev and prod builds, and that thelegacy-esmartifacts still target ES2017. The contents of the bundles do look a bit different (smaller CJS artifacts, slightly different helper output in thelegacy-esmfiles). If you see any behavior differences that look build-related, please file an issue!This is also our first release published via the updated PNPM-based workflow, still using NPM Trusted Publishing. We've also added
pkg.pr.newpreviews for every commit, so you can try out a PR build before it's released.Docs Updates
We've shipped a new combined Redux libraries docs site! The core docs site at redux.js.org now contains the docs for all of our libraries: Redux core and usage guides, Redux Toolkit, React Redux, and Reselect. The prior standalone docs sites for RTK, R-R, and Reselect now redirect to their respective sections of the combined docs. We've also done a major cleanup pass on the docs, deduplicating pages that had similarities (like the Next.js or RTK2 migration pages that lived in both the core and RTK docs), and updating outdated content (modernizing example code snippets, deleting dead links, and making RTK and hooks the default patterns shown). The RTK content now lives at redux.js.org/toolkit/ .
TypeScript 7 Support
TS 7.0 (the native Go port) is now out! We fixed the remaining type errors in RTK when checked by TS 7.0 and 7.1, and TS 7.0 is now part of our CI test matrix.
Per our TS support policy of matching DefinitelyTyped's support window, we've also updated our support matrix to TS 5.6+. As always, RTK may still work with earlier versions, but we no longer test against them.
RTK Query Fixes
useQueryState(and thususeQuery) was passing a new inline selector touseSelectoron every render, and also reading the store directly during render. The selector is now memoized, and the direct store read is gone. This also fixes a bug whereisSuccesscould flip fromfalsetotrueon an unrelated re-render while a query was refetching after an error.isSuccessnow correctly staysfalsein that case.datanow reflects cache updates made viaupdateQueryDatawhile a refetch is in flight, instead of showing the previous result.Polling now reads the current cache state when each poll fires, rather than the state at the time the poll was scheduled. This means
skipPollingIfUnfocusedrespects focus loss that happens after scheduling, and polls stop if their cache entry was removed.We fixed a race where a duplicate query request rejected by the thunk condition could cause queued tag invalidations to run too early and be lost, leaving stale data in the cache.
Tags with falsy ids like
0now invalidate and clean up correctly.Lazy query hooks now re-subscribe correctly when effects restart while the hook state is preserved, such as with Fast Refresh or
<Activity>.Infinite queries no longer trigger
onQueryStartedwhen fetching past the end of the list, and the infinite query hook result type now includes the page error flags.fetchBaseQueryonly treats a URL as absolute if it starts with a scheme.We also fixed an error when rehydrating state for an endpoint name that has no definition.
Other Fixes
createAsyncThunkno longer swallows aborts that happen before thependingaction is dispatched, and now correctly setsrejectedWithValuewhenrejectWithValueis called with a falsy payload.createEntityAdapter'ssetAllnow keeps the last item when given duplicate IDs, matchingsetMany. The sorted adapter'supdateManynow merges multiple updates for the same ID before applying them.combineSlicesnow keeps its internal state proxy cache per instance, so multiple combined reducers no longer interfere with each other.The immutability check middleware now handles circular references in state.
The dynamic middleware now caches its composed middleware chains when the list of middleware hasn't changed.
What's Changed
Additional publishing hardening
codegen: Migrate to oazapfts v7
combineSlices: Scope stateProxyMap per instance to prevent cross-instance proxy collisions
entityAdapter: pre-merge same-ID updates in sorted adapter's updateMany
entityAdapter:setAllwith duplicate IDs keeps last occurrence (consistent withsetMany)query: Reflect cache updates in hook data
Don't swallow
createAsyncThunkaborts that happen before pendingSimplify
useRef<T | undefined>(undefined)usages in RTK Query hooksquery: Prevent
onQueryStartedfrom triggering at end-of-listtypes: Fix tsgo (TS 7.0) type errors and re-enable the native-preview CI job
Modernize CI examples
rtk-query: Only treat a url as absolute when it starts with a scheme
Modern Tooling, Part 1: Oxlint
Modern Tooling, Part 2: Oxfmt
Modern Tooling, Part 3: TSDown and TS6
Modern Tooling, Part 4: Vitest type assertions
Modern Tooling, Part 5: PNPM
Modern Tooling, Part 6: TS7
Modern Tooling, Part 7: vendor
remark-typescript-toolsand update to TS7Modern Tooling, Part 8: Vitest 5
Remove redundant RTK Query example projects
Type the reducer builder implementation explicitly for TS 7.1
toolkit: Flag
rejectedWithValuefor falsyrejectWithValuepayloadsCache unchanged dynamic middleware chains
Preserve delayed invalidations after duplicate query rejection
Read current query state when scheduled polls fire
toolkit: Prevent undefined endpoint reference
toolkit: Tag with a falsy id invalidation and cleanup
rtk-query: Add infinite query page error flags to hook result type
Reinstate lazy queries after preserved effects restart
toolkit: Handle circular references in
immutableStateInvariantMiddlewareFix TS7 errors and update TS matrix
Fix unstable selector reference inside of
useQueryStateandisSuccessduring refetch-after-error
2.12.0
15 May 2026- breaking
toolkit: Switch to native
NoInferutility type Export hook options types for RTK Query hooks
Add TanStack Intent skills for Redux Toolkit
This feature release adds RTK usage skills files (via TanStack Intent) exports the RTK Query hook options types for reusability, fixes issues with infinite query status flags and batching handling, and makes some small TS improvements.
Changelog
Skills Files
We've generated agent skill files that are now included in the RTK package itself in a
skillsfolder. They cover using and migrating to modern RTK, client and server state management, and handling side effects. You can point your agent at these skills yourself, or use TanStack Intent to pick them up.TypeScript Improvements
The types for our RTK Query hook options are now exported, which lets you stop using
Parametersto extract those types for use in your own code.The types for listener middleware matchers were tweaked to allow
interface-based type guards, not justtype-based definitions.The internal
IgnorePathstype was renamed toIgnoredPathsfor consistency.We now use the built-in
NoInferutil that comes with TS 5.4+.Fixes
We fixed handling of the
isSuccessstatus flag when switching infinite query cache entries. This should prevent accidental UI flashes that were occurring due to this flag accidentally flipping.We've added a 100ms timeout fallback to the
autoBatchenhancer'srequestAnimationFrametimer. We had several reports thatrAFdidn't work correctly when used in background tabs / opened windows, and that RTK never updated the UI. This should ensure that the updates flush correctly.What's Changed
Export hook options types for RTK Query hooks
Add TanStack Intent skills for Redux Toolkit
Keep isSuccess: true when switching infinite query cache entries
Allow interface-based type guards as listener matcher
Add setTimeout fallback to raf autoBatch strategy for background tabs
- breaking
toolkit: Switch to native
NoInferutility type
2.11.2
14 Dec 2025Use a normal Error when DOMException isn't available
Include page functions in
TypedUseInfiniteQueryHookResult
This bugfix release updates the
AbortSignalhandling to fall back ifDOMExceptionisn't available (such as RN environments), and updates theTypedUseInfiniteQueryHookResulttype to correctly includefetchNextPage/fetchPreviousPagefields.Changelog
Bugfixes
The
AbortSignalchanges in 2.11.1 usedDOMExceptionin a couple places to match the expected behavior ofAbortSignal, but turns out that's not available in environments like React Native. We've updated the logic to fall back to a plainErrorifDOMExceptionisn't available.The
TypedUseInfiniteQueryHookResulttype wasn't correctly including thefetchNextPage/fetchPreviousPagefields, and now it does.What's Changed
Use a normal Error when DOMException isn't available
Include page functions in
TypedUseInfiniteQueryHookResult
2.11.1
8 Dec 2025Use trusted publishing and harden workflows
Update TS typecheck matrix and add TS native job
Officially document TS support
This bugfix release fixes an issue with our internal
AbortSignalhandling that was reported as causing an error in a rare reset situation. We've also restructured our publishing process to use NPM Trusted Publishing, and updated our TS support matrix to only support TS 5.4+.Changelog
Publishing Changes
We've previously done most of our releases semi-manually locally, with various release process CLI tools. With the changes to NPM publishing security and the recent wave of NPM attacks, we've updated our publishing process to solely use NPM Trusted Publishing via workflows. We've also done a hardening pass on our own CI setup.
We had done a couple releases via CI workflows previously, and later semi-manual releases caused PNPM to warn that RTK was no longer trusted. This release should be trusted and will resolve that issue.
Thanks to the e18e folks and their excellent guide at e18e.dev/docs/publishing for making this process easier!
TS Support Matrix Updates
We've previously mentioned rolling changes to our TS support matrix in release notes, but didn't officially document our support policy. We've added a description of the support policy (last 2 years of TS releases, matching DefinitelyTyped) and the current oldest TS version we support in the docs:
As of today, we've updated the support matrix to be TS 5.4+ . As always, it's possible RTK will work if you're using an earlier version of TS, but we don't test against earlier versions and don't support any issues with those versions.
We have run an initial test with the upcoming TS 7.0 native
tsgorelease. We found a couple minor issues with our own TS build and test setup, but no obvious issues with using RTK with TS 7.0.Bug Fixes
A user reported a rare edge case where the combination of
resetApiStateandretry()could lead to an error calling anAbortController. We've restructured ourAbortControllerhandling logic to avoid that (and simplified a bit of our internals in the process).What's Changed
Use trusted publishing and harden workflows
Update TS typecheck matrix and add TS native job
Officially document TS support
Tweaks to AbortSignal logic
2.11.0
23 Nov 2025Update Immer to v11 for better update performance
Compare paths against Regex instances in ignoredPaths
Add refetchCachedPages option for infinite query
This feature release upgrades our Immer dependency to v11 to pick up the additional recent performance optimizations, adds a new
refetchCachedPagesoption to allow only fetching the first cached page, and fixes an issue with regex ignore paths in the immutability middleware.Changelog
Immer v11 Performance Improvements
As described in the release notes for v2.10.0, we recently put significant effort into profiling Immer, and contributed several PRs that aimed to optimize its update performance.
v2.10.0 updated to use Immer 10.2.0, which added the first smaller set of perf updates. That included a new Immer option to disable "strict iteration" to speed up iterating copied objects, and we specifically applied that change in RTK under the assumption that standard plain JS objects as Redux state shouldn't have unusual keys anyway. Overall, this appears to boost Immer update perf by ~+20% over v10.1 depending on update scenario.
Immer v11.0.0 was just released and contains the second perf PR, a major internal architectural rewrite to change the update finalization implementation from a recursive tree traversal to a set of targeted updates based on accessed and updated fields. Based on the benchmarks in the PR, this adds another ~+5% perf boost over the improvements in v10.2, again with variations depending on update scenario. In practice, the actual improvement may be better than that - the benchmarks list includes some array update cases which actually got a bit slower (and thus drag down the overall average), and a majority of update scenarios show anywhere from +25% to +60% faster than Immer v10.1!
As a practical example, we have an RTK Query stress test benchmark where we mount 1000 components with query hooks at once, unmount, then remount them. We ran the same benchmark steps for RTK 2.9 and Immer 10.1, and then RTK 2.10+ and Immer 11. The overall scripting time dropped by about 30% (3330ms -> 2350ms), and the amount of time spent in Immer methods and the RTK reducers dropped significantly:
Based on this, it appears to be a major improvement overall.
As with the instructions in v2.10.0: if by some chance your Redux app state relies on non-string keys, you can still manually call
setUseStrictIteration(true)in your app code to retain compatibility there, but we don't expect that standard Redux apps will have to worry about that.There are still two outstanding Immer perf PRs that may offer further improvements: one that adds an optional plugin to override array methods to avoid proxy creation overhead, and another experimental tweak to shallow copying that may be better with larger object sizes.
New
refetchCachedPagesOptionRTK Query's infinite query API was directly based on React Query's approach, including the pages cache structure and refetching behavior. By default, that means that when you trigger a refetch, both R-Q and RTKQ will try to sequentially refetch all pages currently in that cache entry. So, if there were 5 pages cached for an entry, they will try to fetch pages 0...4, in turn.
Some users have asked for the ability to only refetch the first page. This can be accomplished somewhat manually by directly updating the cache entry to eliminate the old pages and then triggering a refetch, but that's admittedly not very ergonomic.
We've merged a contributed PR that adds a new
refetchCachedPagesflag. This can be defined as part of infinite query endpoints, passed as an option to infinite query hooks, or passed as an option ininitiate()calls or hookrefetch()methods. If set torefetchCachedPages: false, it will only refetch the first page in the cache and not the remaining pages, thus shrinking the cache from N pages to 1 page.Other Fixes
We merged a fix to the immutability dev middleware where it was treating
ignoredPathregexes as strings and not actually testing them correctly.What's Changed
Update Immer to v11 for better update performance
Compare paths against Regex instances in ignoredPaths
Add refetchCachedPages option for infinite query
2.10.1
4 Nov 2025Fix window SSR breakage
This bugfix release fixes an issue with
windowaccess breaking in SSR due to the byte-shaving work in 2.10.What's Changed
Fix window SSR breakage
2.10.0
4 Nov 2025Improve perf for internal map/filter ops
Update Immer dep to 10.2 and disable strict iteration for perf
Deduplicate imports
This feature release updates our Immer dep to 10.2 to pick up its performance improvements, has additional byte-shaving and internal performance updates, and fixes a
combineSlicestype issue.Changelog
Immer Performance Improvements
Redux Toolkit has been built around Immer since the very first prototype in 2018. Use of Immer as the default in
createSlicedirectly eliminated accidental mutations as a class of errors in Redux apps, and drastically simplified writing immutable updates in reducers.We've had various issues filed over the years asking to make Immer optional, or raising concerns about Immer's perf. Immer is indeed slower than writing immutable updates by hand, but our stance has always been that Immer's DX is absolutely worth whatever modest perf cost it might incur, and that reducers are usually not the bottleneck in Redux apps anyway - it's usually the cost of updating the UI that's more expensive.
However, a year ago an issue was filed with some specific complaints about Immer perf being very slow. We investigated, ran benchmarks, and filed an Immer issue confirming that it had gotten noticeably slower over time. Immer author Michel Weststrate agreed, and said there were some potential tweaks and architectural changes that could be made, but didn't have time to look into them himself.
A couple months ago, we started investigating possible Immer perf improvements ourselves, including profiling various scenarios and comparing implementations of other similar immutable update libraries. After extensive research and development, we were able to file several PRs to improve Immer's perf: a set of smaller tweaks around iteration and caching, a couple much larger architectural changes, and a potential change to copying objects.
Immer 10.2.0 contains the first set of smaller perf improvements, and this RTK release updates our dependency to 10.2 to pick up those changes.
One important behavior note here: Earlier versions of Immer (8, 9, 10.1) added more handling for edge cases like symbol keys in objects. These changes made sense for correctness, but also contributed to the slowdown. Immer 10.2 now includes a new
setUseStrictIterationoption to allow only copying string keys in objects (usingObject.keys()instead ofReflect.ownKeys()), but keeps the option asstrict: truefor compatibility with its own users. That default will likely change in Immer 11.For RTK 2.10.0, we specifically import and call
setUseStrictIteration(false), under the assumption that standard Redux state usage only involves string keys in plain JS objects! This should provide a ~10% speedup for Immer update operations. Given that expectation, we believe this is a reasonable feature change and only needs a minor version bump.If by some chance you are using symbol keys in your Redux state, or in other Immer-powered updates in your Redux app, you can easily revert to the previous behavior by calling
setUseStrictIteration(true)in your own app code.Based on discussions with Michel, Immer v11 should come out in the near future with additional architectural changes for better perf, including optional support for faster array methods that would be available as an Immer plugin adding ~2KB bundle size. We will likely not turn that plugin on by default, but recommend that users enable it if they do frequent array ops in reducers.
We're happy to have contributed these perf improvements to Immer, and that they will benefit not just RTK users but all Immer users everywhere!
You can follow the additional discussion and progress updates in the main Immer perf update tracking issue.
Additional RTK Perf Improvements
We've tweaked some places where we were doing repeated
filter().map().map()calls to micro-optimize those loops.RTKQ tag invalidation was always reading from proxy-wrapped arrays when rewriting provided tags. It now reads from the plain arrays instead, providing a modest speedup.
We previously found that ESBuild wasn't deduplicating imports from the same libraries in separate files bundled together (ie
import { useEffect as useEffect2/3/4/ } from 'react'). We've restructured our internals to ensure all external imports are only pulled in once.We've done some extensive byte-shaving in various places in the codebase. The byte-shaving and import deduplication saves about 0.6K min from the RTKQ core, and 0.2K min from the RTKQ React bundle.
Other Changes
combineSlicesnow better handles cases wherePreloadedStatemight not match the incoming type, such as persisted values.What's Changed
Improve perf for internal map/filter ops
Update Immer dep to 10.2 and disable strict iteration for perf
Deduplicate imports
Byte-shave various chunks of code
combineSlices: Infer PreloadedState properly from provided slice
2.9.2
22 Oct 2025Fix potential subscription leakage in SSR environments
Improve
fetchBaseQuerydefault headers handlingRespect maxRetries for unexpected errors
This bugfix release fixes a potential internal data leak in SSR environments, improves handling of headers in
fetchBaseQuery, improvesretryhandling for unexpected errors and request aborts, and fixes a longstanding issue withprefetchleaving an unused subscription. We've also shipped a newgraphqlRequestBaseQueryrelease with updated dependencies and better error handling.Changelog
Internal Subscription Handling
We had a report that a Redux SSR app had internal subscription data showing up across different requests. After investigation, this was a bug introduced by the recent RTKQ perf optimizations, where the internal subscription fields were hoisted outside of the middleware setup and into
createApiitself. This meant they existed outside of the per-store-instance lifecycle. We've reworked the logic to ensure the data is per-store again. We also fixed another issue that miscalculated when there was an active request while checking for cache entry cleanup.Note that no actual app data was leaked in this case, just the internal subscription IDs that RTKQ uses in its own middleware to track the existence of subscriptions per cache entry.
fetchBaseQueryHeadersWe've updated
fetchBaseQueryto avoid settingcontent-typein cases where a non-JSONifiable value likeFormDatais being passed as the request body, so that the browser can set that content type itself. It also now sets theacceptheader based on the selectedresponseHandler(JSON or text).retryBehavior and CleanupThe
retryutil now respects themaxRetriesoption when catching unknown errors in addition to the existing known errors logic. It also now checks the request'sAbortSignaland will stop retrying if aborted.In conjunction with that, dispatching
resetApiStatewill now abort all in-flight requests.The
prefetchutil andusePrefetchhook had a long-standing issue where they would create a subscription for a cache entry, but there was no way to clean up that subscription. This meant that the cache entry was effectively permanent. They now initiate the request without adding a subscription. This will fetch the cache entry and leave it in the store for thekeepUnusedDataForperiod as intended, giving your app time to actually subscribe to the value (such as prefetching the cache entry in a route handler, and then subscribing in a component).graphqlRequestBaseQueryWe've published
@rtk-query/graphql-request-base-queryv2.3.2, which updates thegraphql-requestdep to ^7. We also fixed an issue where the error handling rethrew unknown errors - it now returns{error}as a base query is supposed to.What's Changed
Fix potential subscription leakage in SSR environments
Improve
fetchBaseQuerydefault headers handlingRespect maxRetries for unexpected errors
Update graphql-request dependency to include version ^7.0.0
Add
retryabort handling and abort onresetApiStateDon't create subscriptions for prefetch calls
2.9.1
17 Oct 2025entityAdapter: Ensure sorted addMany keeps first occurrence of duplicate ids
entityAdapter: Ensure sorted setMany keeps just unique IDs in state.ids
types: Ensure non-undefined
dataon isSuccess withexactOptionalPropertyTypes
This bugfix release fixes how sorted entity adapters handle duplicate IDs, tweaks the TS types for RTKQ query state cache entries to improve how the
datafield is handled, and adds better cleanup for long-running listener middleware effects.What's Changed
entityAdapter: Ensure sorted addMany keeps first occurrence of duplicate ids
entityAdapter: Ensure sorted setMany keeps just unique IDs in state.ids
types: Ensure non-undefined
dataon isSuccess withexactOptionalPropertyTypesAllow executing effects that have become unsubscribed to be canceled by
listenerMiddleware.clearListeners
2.9.0
3 Sep 2025Add RawResultType as a type-only property on endpoints
Allow passing an array of specific schemas to skip
types: re-exporting WritableDraft from immer
This feature release rewrites RTK Query's internal subscription and polling systems and the
useStableQueryArgshook for better perf, adds automaticAbortSignalhandling to requests still in progress when a cache entry is removed, fixes a bug with thetransformResponseoption for queries, adds a newbuilder.addAsyncThunkmethod, and fixes assorted other issues.Changelog
RTK Query Performance Improvements
We had reports that RTK Query could get very slow when there were thousands of subscriptions to the same cache entry. After investigation, we found that the internal polling logic was attempting to recalculate the minimum polling time after every new subscription was added. This was highly inefficient, as most subscriptions don't change polling settings, and it required repeated O(n) iteration over the growing list of subscriptions. We've rewritten that logic to debounce the update check and ensure a max of one polling value update per tick for the entire API instance.
Related, while working on the request abort changes, testing showed that use of plain
Records to hold subscription data was inefficient because we have to iterate keys to check size. We've rewritten the subscription handling internals to useMaps instead, as well as restructuring some additional checks around in-flight requests.These two improvements drastically improved runtime perf for the thousands-of-subscriptions-one-cache-entry repro, eliminating RTK methods as visible hotspots in the perf profiles. It likely also improves perf for general usage as well.
We've also changed the implementation of our internal
useStableQueryArgshook to avoid callingserializeQueryArgson its value, which can avoid potential perf issues when a query takes a very large object as its cache key.Note
The internal logic switched from serializing the query arg to doing reference checks on nested values. This means that if you are passing a non-POJO value in a query arg, such as
useSomeQuery({a: new Set()}), and you haverefetchOnMountOrArgChangeenabled, this will now trigger refeteches each time as theSetreferences are now considered different based on equality instead of serialization.Abort Signal Handling on Cleanup
We've had numerous requests over time for various forms of "abort in-progress requests when the data is no longer needed / params change / component unmounts / some expensive request is taking too long". This is a complex topic with multiple potential use cases, and our standard answer has been that we don't want to abort those requests - after all, cache entries default to staying in memory for 1 minute after the last subscription is removed, so RTKQ's cache can still be updated when the request completes. That also means that it doesn't make sense to abort a request "on unmount".
However, it does then make sense to abort an in-progress request if the cache entry itself is removed. Given that, we've updated our cache handling to automatically call the existing
resPromise.abort()method in that case, triggering theAbortSignalattached to thebaseQuery. The handling at that point depends on your app -fetchBaseQueryshould handle that, a custombaseQueryorqueryFnwould need to listen to theAbortSignal.We do have an open issue asking for further discussions of potential abort / cancelation use cases and would appreciate further feedback.
New Options
The builder callback used in
createReducerandcreateSlice.extraReducersnow hasbuilder.addAsyncThunkavailable, which allows handling specific actions from a thunk in the same way that you could define a thunk insidecreateSlice.reducers:const slice = createSlice({ name: 'counter', initialState: { loading: false, errored: false, value: 0, }, reducers: {}, extraReducers: (builder) => builder.addAsyncThunk(asyncThunk, { pending(state) { state.loading = true }, fulfilled(state, action) { state.value = action.payload }, rejected(state) { state.errored = true }, settled(state) { state.loading = false }, }), })createApiand individual endpoint definitions now accept askipSchemaValidationoption with an array of schema types to skip, ortrueto skip validation entirely (in case you want to use a schema for its types, but the actual validation is expensive).Bug Fixes
The infinite query implementation accidentally changed the query internals to always run
transformResponseif provided, including if you were usingupsertQueryData(), which then broke. It's been fixed to only run on an actual query request.The internal changes to the structure of the
state.api.providedstructure broke our handling ofextractRehydrationInfo- we've updated that to handle the changed structure.The infinite query status fields like
hasNextPageare now a looser type ofbooleaninitially, rather than strictlyfalse.TS Types
We now export Immer's
WritableDrafttype to fix another non-portable types issue.We've added an
api.endpoints.myEndpoint.types.RawResultTypetypes-only field to match the other available fields.What's Changed
Add RawResultType as a type-only property on endpoints
Allow passing an array of specific schemas to skip
types: re-exporting WritableDraft from immer
Remove Serialisation from useStableQueryArgs
Add addAsyncThunk method to reducer map builder
Only run
transformResponsewhen aqueryis usedAssorted bugfixes for 2.8.3
Abort pending requests if the cache entry is removed
Update TS CI config
Rewrite subscription handling and polling calculations for better perf
2.8.2
14 May 2025Restructure build config to fix RTKQ externals
This bugfix release fixes a bundle size regression in RTK Query from the build and packaging changes in v2.8.0.
If you're using v2.8.0 or v2.8.1, please upgrade to v2.8.2 right away to resolve that bundle size issue!
Changelog
RTK Query Bundle Size
In v2.8.0, we reworked our packaging setup to better support React Native. While there weren't many meaningful code changes, we did alter our bundling build config file. In the process, we lost the config options to externalize the
@reduxjs/toolkitcore when building the RTK Query nested entry points. This resulted in a regression where the RTK core code also got bundled directly into the RTK Query artifacts, resulting in a significant size increase.This release fixes the build config and restores the previous RTKQ build artifact sizes.
What's Changed
Restructure build config to fix RTKQ externals
2.8.1
8 May 2025browserexportscondition
This bugfix release makes an additional update to the package config to fix a regression that happened with Jest and
jest-environment-jsdom.Caution
This release had a bundle size regression. Please update to v2.8.2 to resolve that issue.
Changes
More Package Updates
After releasing v2.8.0, we got reports that Jest tests were breaking. After investigation we concluded that
jest-environment-jsdomwas looking at the newbrowserpackage exports condition we'd added to better support JSPM, finding an ESM file containing theexportkeyword, and erroring because it doesn't support ES modules correctly.#4971 listed several viable workarounds, but this is enough of an issue we wanted to fix it directly. We've tweaked the package exports setup again, and it appears to resolve the issue with Jest.
What's Changed
browserexportscondition
2.8.0
6 May 2025React-Native package exports
Add queryArg as 5th param to page param functions
This feature release improves React Native compatibility by updating our package exports definitions, and adds
queryArgas an additional parameter to infinite query page param functions.Caution
This release had a bundle size regression, as well as a breakage with
jest-environment-jsdom. Please update to v2.8.2 to resolve those issues.Changelog
Package Exports and React Native Compatibility
Expo and the Metro bundler have been adding improved support for the
exportsfield inpackage.jsonfiles, but those changes started printing warnings due to how some of our package definitions were configured.We've reworked the package definitions (again!), and this should be resolved now.
Infinite Query Page Params
The signature for the
getNext/PreviousPageParamfunctions has been:( lastPage: DataType, allPages: Array<DataType>, lastPageParam: PageParam, allPageParams: Array<PageParam>, ) => PageParam | undefined | nullThis came directly from React Query's API and implementation.
We've had some requests to make the endpoint's
queryArgavailable in page param functions. For React Query, that isn't necessary because the callbacks are defined inline when you call theuseInfiniteQueryhook, so you've already got the query arg available in scope and can use it. Since RTK Query defines these callbacks as part of the endpoint definition, the query arg isn't in scope.We've added
queryArgas an additional 5th parameter to these functions in case it's needed.Other Changes
We've made a few assorted docs updates, including replacing the search implementation to now use a local index generated on build (which should be more reliable and also has a nicer results list uI), and fixing some long-standing minor docs issues.
What's Changed
React-Native package exports
Add queryArg as 5th param to page param functions
2.7.0
16 Apr 2025The
lifecycleApi.updateCachedDatamethod is now correctly availableThe
skipoption now correctly works for infinite query hooksInfinite query
fulfilledactions now include themetafield from the base query (such as{request, response}). For cases where multiple pages are being refetched, this will be the meta from the last page fetched.
RTK has hit Stage 2.7! :rofl: This feature release adds support for Standard Schema validation in RTK Query endpoints, fixes several issues with infinite queries, improves perf when infinite queries provide tags, adds a dev-mode check for duplicate middleware, and improves reference stability in slice selectors and infinite query hooks.
Changelog
Standard Schema Validation for RTK Query
Apps often need to validate responses from the server, both to ensure the data is correct, and to help enforce that the data matches the expected TS types. This is typically done with schema libraries such as Zod, Valibot, and Arktype. Because of the similarities in usage APIs, those libraries and others now support a common API definition called Standard Schema, allowing you to plug your chosen validation library in anywhere Standard Schema is supported.
RTK Query now supports using Standard Schema to validate query args, responses, and errors. If schemas are provided, the validations will be run and errors thrown if the data is invalid. Additionally, providing a schema allows TS inference for that type as well, allowing you to omit generic types from the endpoint.
Schema usage is per-endpoint, and can look like this:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import * as v from 'valibot' const postSchema = v.object({ id: v.number(), name: v.string(), }) type Post = v.InferOutput<typeof postSchema> const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/' }), endpoints: (build) => ({ getPost: build.query({ // infer arg from here query: ({ id }: { id: number }) => `/post/${id}`, // infer result from here responseSchema: postSchema, }), getTransformedPost: build.query({ // infer arg from here query: ({ id }: { id: number }) => `/post/${id}`, // infer untransformed result from here rawResponseSchema: postSchema, // infer transformed result from here transformResponse: (response) => ({ ...response, published_at: new Date(response.published_at), }), }), }), })If desired, you can also configure schema error handling with the
catchSchemaFailureoption. You can also disable actual runtime validation withskipSchemaValidation(primarily useful for cases when payloads may be large and expensive to validate, but you still want to benefit from the TS type inference).See the "Schema Validation" docs section in the
createApireference and the usage guide sections on queries, infinite queries, and mutations, for more details.Infinite Query Fixes
This release fixes several reported issue with infinite queries:
The
lifecycleApi.updateCachedDatamethod is now correctly availableThe
skipoption now correctly works for infinite query hooksInfinite query
fulfilledactions now include themetafield from the base query (such as{request, response}). For cases where multiple pages are being refetched, this will be the meta from the last page fetched.useInfiniteQuerySubscriptionnow returns stable references forrefetchand thefetchNext/PreviousPagemethods
upsertQueryEntries, Tags Performance and API State StructureWe recently published a fix to actually process per-endpoint
providedTagswhen usingupsertQueryEntries. However, this exposed a performance issue - the internal tag handling logic was doing repeated O(n) iterations over all endpoint+tag entries in order to clear out existing references to that cache key. In cases where hundreds or thousands of cache entries were being inserted, this became extremely expensive.We've restructured the
state.api.provideddata structure to handle reverse-mapping between tags and cache keys, which drastically improves performance in this case. However, it's worth noting that this is a change to that state structure. This shouldn't affect apps, because the RTKQ state is intended to be treated as a black box and not generally directly accessed by user app code. However, it's possible someone may have depended on that specific state structure when writing a custom selector, in which case this would break. An actual example of this is the Redux DevTools RTKQ panel, which iterates the tags data while displaying cache entries. That did break with this change. Prior to releasing RTK 2.7,we released Redux DevTools 3.2.10, which includes support for both the old and newstate.api.provideddefinitions.TS Support Matrix Updates
Following with the DefinitelyTyped support matrix, we've officially dropped support for TS 5.0, and currently support TS 5.1 - 5.8. (RTK likely still works with 5.0, but we no longer test against that in CI.)
Duplicate Middleware Dev Checks
configureStorenow checks the final middleware array for duplicate middleware references. This will catch cases such as accidentally adding the same RTKQ API middleware twice (such as addingbaseApi.middlewareandinjectedApi.middlweware- these are actually the same object and same middleware).Unlike the other dev-mode checks, this is part of
configureStoreitself, notgetDefaultMiddleware().This can be configured via the new
duplicateMiddlewareCheckoption.Other Changes
createEntityAdapternow correctly handles adding an item and then applying multiple updates to it.The generated
combineSlicesselectors will now return the same placeholder initial state reference for a given slice, rather than returning a new initial state reference every time.useQueryhooks should now correctly refetch after dispatchingresetApiState.What's Changed
Process multiple update for the same new entity
Add infinite query support for updateCachedData
Add infinite query skip support
Cache initial state in injected scenarios
Rewrite providedTags handling for better perf
Allow standard schemas to validate endpoint values
rtk-query:
useQueryhook does not refetch afterresetApiStateDrop TS 5.0 from compat matrix
Add duplicate middleware dev check to configureStore
Improve duplicate middleware error and save build output
Add meta handling for infinite queries
Improve stability of useInfiniteQuerySubscription's return value
Add
catchSchemaFailure, and docs for RTKQ schema features
2.6.1
7 Mar 2025matchFulfilledandprovidesTagsnow get the correct response typesWe've added pre-typed
Type*types to represent infinite queries, similar to the existing pre-defined types for queries and mutationsselectCachedArgsForQuerynow supports fetching args for infinite query endpoints
This bugfix release fixes several assorted types issues with the initial infinite query feature release, and adds support for an optional signal argument to
createAsyncThunk.Changelog
Infinite Query Fixes
We've fixed several types issues that were reported with infinite queries after the 2.6.0 release:
matchFulfilledandprovidesTagsnow get the correct response typesWe've added pre-typed
Type*types to represent infinite queries, similar to the existing pre-defined types for queries and mutationsselectCachedArgsForQuerynow supports fetching args for infinite query endpointsWe fixed some TS type portability issues with infinite queries that caused errors when generating TS declarations
useInfiniteQueryState/Subscriptionnow correctly expect just the query arg, not the combined{queryArg, pageParam}object
Other Improvements
createAsyncThunknow accepts an optional{signal}argument. If provided, the internal AbortSignal handling will tie into that signal.upsertQueryEntriesnow correctly generates provided tags for upserted cache entries.What's Changed
Fix assorted infinite query types
Add providesTags handling for upsertQueryEntries
Add infinite query type support for selectCachedArgsForQuery
Add more Typed wrappers and make sure they're all exported
Fix infinite query type portability issues
Support passing an external abortsignal to createAsyncThunk
2.6.0
23 Feb 2025RTKQ Infinite Query integration
This feature release adds infinite query support to RTK Query.
Changelog
RTK Query Infinite Query support
Since we first released RTK Query in 2021, we've had users asking us to add support for "infinite queries" - the ability to keep fetching additional pages of data for a given endpoint. It's been by far our most requested feature. Until recently, our answer was that we felt there were too many use cases to support with a single API design approach.
Last year, we revisited this concept and concluded that the best approach was to mimic the flexible infinite query API design from React Query. We had additional discussions with @tkdodo , who described the rationale and implementation approach and encouraged us to use their API design, and @riqts provided an initial implementation on top of RTKQ's existing internals.
We're excited to announce that this release officially adds full infinite query endpoint support to RTK Query!
Using Infinite Queries
As with React Query, the API design is based around "page param" values that act as the query arguments for fetching a specific page for the given cache entry.
Infinite queries are defined with a new
build.infiniteQuery()endpoint type. It accepts all of the same options as normal query endpoints, but also needs an additionalinfiniteQueryOptionsfield that specifies the infinite query behaviors. With TypeScript, you must supply 3 generic arguments:build.infiniteQuery<ResultType, QueryArg, PageParam>, whereResultTypeis the contents of a single page,QueryArgis the type passed in as the cache key, andPageParamis the value used to request a specific page.The endpoint must define an
initialPageParamvalue that will be used as the default (and can be overridden if desired). It also needs agetNextPageParamcallback that will calculate the params for each page based on the existing values, and optionally agetPreviousPageParamcallback if reverse fetching is needed. Finally, amaxPagesoption can be provided to limit the entry cache size.The
queryandqueryFnmethods now receive a{queryArg, pageParam}object, instead of just thequeryArg.For the cache entries and hooks, the
datafield is now an object like{pages: ResultType, pageParams: PageParam>. This gives you flexibility in how you use the data for rendering.const pokemonApi = createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/pokemon' }), endpoints: (build) => ({ // 3 TS generics: page contents, query arg, page param getInfinitePokemonWithMax: build.infiniteQuery<Pokemon[], string, number>({ infiniteQueryOptions: { // Must provide a default initial page param value initialPageParam: 1, // Optionally limit the number of cached pages maxPages: 3, // Must provide a `getNextPageParam` function getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => lastPageParam + 1, // Optionally provide a `getPreviousPageParam` function getPreviousPageParam: ( firstPage, allPages, firstPageParam, allPageParams, ) => { return firstPageParam > 0 ? firstPageParam - 1 : undefined }, }, // The `query` function receives `{queryArg, pageParam}` as its argument query({ queryArg, pageParam }) { return `/type/${queryArg}?page=${pageParam}` }, }), }), })As with all RTKQ functionality, the core logic is UI-agnostic and does not require React. However, using the RTKQ React entry point will also auto-generate
useInfiniteQueryhooks for these endpoints. Infinite query hooks fetch the initial page, then providefetchNext/PreviousPagefunctions to let you trigger requests for more pages.function PokemonList({ pokemonType = 'fire', }: { pokemonType?: string ) { const { data, isFetching, isSuccess, fetchNextPage, fetchPreviousPage, refetch, } = api.useGetInfinitePokemonInfiniteQuery(pokemonType) const handlePreviousPage = async () => { const res = await fetchPreviousPage() } const handleNextPage = async () => { const res = await fetchNextPage() } // `data` is a `{pages, pageParams}` object. // You can use those to render whatever UI you need. // In this case, flatten per-page arrays of results for this endpoint // into a single array to render a list. const allPokemon = data?.pages.flat() ?? []; // render UI with pages, show loading state, fetch as needed }Docs and Examples
The RTK Query docs have been updated with new content and explanations for infinite queries:
We've also added a new infinite query example app in the repo that shows several usage patterns like pagination, cursors, infinite scrolling, and limit+offset queries.
Notes
As with all new features and functionality, more code does mean an increase in bundle size.
We did extensive work to byte-shave and optimize the final bundle size for this feature. Final estimates indicate that this adds about 4.2Kmin to production bundles. That's comparable to React Query's infinite query support size.
However, given RTKQ's current architecture, that bundle size increase is included even if you aren't using any infinite query endpoints in your application. Given the significant additional functionality, that seems like an acceptable tradeoff. (And as always, having this kind of functionality built into RTKQ means that your app benefits when it uses this feature without having to add a lot of additional code to your own app, which would likely be much larger.)
Longer-term, we hope to investigate reworking some of RTKQ's internal architecture to potentially make some of the features opt-in for better bundle size optimizations, but don't have a timeline for that work.
What's Changed
Infinite Query API
RTKQ Infinite Query integration
and numerous specific sub-PRs that went into that integration PR as I worked through the implementation over the last few months.
2.5.1
26 Jan 2025Ensure upserted cache entries always get written
This bugfix release fixes a logic issue with the new
upsertQueryEntriesutil that sometimes kept entries in apendingstate indefinitely.Changelog
upsertQueryEntriesfixesUsers reported that in some cases, use of
upsertQueryEntriesto insert RTKQ cache entries prevented any further refetches of that data from happening. After investigation, we found a logic mismatch for how we handle upserts vs the existingupsertQueryDatautil, which meant that sometimes the entry would be left in apendingstate expecting afulfilledaction from a request ID that would never happen.This release fixes that issue and ensures the updates and refetches happen correctly.
What's Changed
Ensure upserted cache entries always get written
2.5.0
11 Dec 2024Migrate to React 19
Handle additional
serializeQueryArgs+skipTokencase
This feature release updates the React peer dependency to work with React 19, and fixes an additional skip token issue.
Changelog
React 19 Compat
React 19 was just released! We've updated our peer dep to accept React 19, and updated our runtime and type tests to check against both React 18 and 19.
Also see React-Redux v9.2.0 for the same peer dep update.
Other Fixes
We previously fixed an issue with the RTKQ core where
serializeQueryArgscallbacks could be called withskipToken, potentially leading to errors. We've fixed an additional location in theuseQueryhooks where that could happen as well.What's Changed
Migrate to React 19
Handle additional
serializeQueryArgs+skipTokencase
2.4.0
28 Nov 2024Add example to reproduce defect of serializeQueryArgs with skipToken
Read
window.rAFlater to allow fake timers to work correctlyAdd type helpers for
OnQueryStartedcallbacks
This feature release includes multiple tweaks and fixes to RTK Query functionality, additional exported TS types, and drops support for TS versions earlier than 5.0.
Changelog
RTK Query Improvements
Lazy query hooks can now be reset.
retry.failnow acceptsmetaas a second argument.Tag invalidation arrays now ignore nullish values.
We did some small internal refactoring around Maps and default values that shrank bundle size slightly.
Bugfixes
Passing
skipTokento a query hook now bails out before running any other logic, which fixes cases whereserializeQueryArgspreviously threw an error because there were no args to process.The
autoBatchEnhancernow readswindow.requestAnimationFramelater, which it to work properly with Jest fake timers.We fixed cases where the hook result
isSuccessflag would briefly flicker tofalsewhen switched to a different cache entry that was uninitialized, and would briefly flicker totruewhen refetching a query that previously errored.The listener middleware previously had inconsistent logic checks for comparing against existing listener entries (effect + type, vs effect only). It now always checks both effect + type.
Additional TS Types
We now export
Typed[Query|Mutation]OnQueryStartedhelpers to let you defineonQueryStartedcallbacks outside ofcreateApiif desired.We also now export a
CreateAsyncThunkFunctiontype that can be used to type userland wrappers aroundcreateAsyncThunk.TS Support Matrix Updates
We've historically tried to maintain TS backwards compatibility as long as possible, and made occasional updates to our TS support matrix in minor versions over time. As of RTK 2.3.0, we officially supported back through TS 4.7.
As of this release, we're tweaking that support policy to match the policy used by DefinitelyTyped:
Definitely Typed only tests packages on versions of TypeScript that are less than 2 years old
imageGiven that, we've dropped official support for TS versions earlier than 5.0. (RTK may work with those versions, but we no longer test against them and won't try to fix issues with those versions.)
We'll continue to update our TS support matrix over time based on that 2-year rolling window.
What's Changed
Add example to reproduce defect of serializeQueryArgs with skipToken
Read
window.rAFlater to allow fake timers to work correctlyAdd type helpers for
OnQueryStartedcallbacksAdd a type for
createAsyncThunkwithout thewithTypesmethodAdd ability to reset lazy query hooks
Ignore nullish values in tag invalidations
Allow passing meta to retry.fail, and passing baseQuery to ensure types match
Keep
isSuccess: truewhen switching to an uninitialized cache entryKeep
isSuccessconsistent when refetching after an errorUpdate to new version of upsert proposal, and fix listener equality checks
2.3.0
14 Oct 2024arg: the URL string orFetchArgsobject that was passed in tofetchBaseQueryfor this endpointextraOptions: any extra options that were provided to the base queryFix serializeQueryArgs type
This feature release adds a new RTK Query
upsertQueryEntriesutil to batch-upsert cache entries more efficiently, passes through additional values for use inprepareHeaders, and exports additional TS types around query options and selectors.Changelog
upsertQueryEntriesRTK Query already had an
upsertQueryDatathunk that would upsert a single cache entry. However, some users wanted to upsert many cache entries (potentially hundreds or thousands), and found thatupsertQueryDatahad poor performance in those cases. This is becauseupsertQueryDataruns the full async request handling sequence, including dispatching bothpendingandfulfilledactions, each of which run the main reducer and update store subscribers. That means there's2Nstore / UI updates per item, so upserting hundreds of items becomes extremely perf-intensive.RTK Query now includes an
api.util.upsertQueryEntriesaction that is meant to handle the batched upsert use case more efficiently. It's a single synchronous action that accepts an array of many{endpointName, arg, value}entries to upsert. This results in a single store update, making this vastly better for performance vs many individualupsertQueryDatacalls.We see this as having two main use cases. The first is prefilling the cache with data retrieved from storage on app startup (and it's worth noting that
upsertQueryEntriescan accept entries for many different endpoints as part of the same array).The second is to act as a "pseudo-normalization" tool. RTK Query is not a "normalized" cache. However, there are times when you may want to prefill other cache entries with the contents of another endpoint, such as taking the results of a
getPostslist endpoint response and prefilling the individualgetPost(id)endpoint cache entries, so that components that reference an individual item endpoint already have that data available.Currently, you can implement the "pseudo-normalization" approach by dispatching
upsertQueryEntriesin an endpoint lifecycle, like this:const api = createApi({ endpoints: (build) => ({ getPosts: build.query<Post[], void>({ query: () => '/posts', async onQueryStarted(_, { dispatch, queryFulfilled }) { const res = await queryFulfilled const posts = res.data // Pre-fill the individual post entries with the results // from the list endpoint query dispatch( api.util.upsertQueryEntries( posts.map((post) => ({ endpointName: 'getPost', arg: { id: post.id }, value: post, })), ), ) }, }), getPost: build.query<Post, Pick<Post, 'id'>>({ query: (post) => `post/${post.id}`, }), }), })Down the road we may add a new option to query endpoints that would let you provide the mapping function and have it automatically update the corresponding entries.
For additional comparisons between
upsertQueryDataandupsertQueryEntries, see theupsertQueryEntriesAPI reference.prepareHeadersOptionsThe
prepareHeaderscallback forfetchBaseQuerynow receives two additional values in theapiargument:arg: the URL string orFetchArgsobject that was passed in tofetchBaseQueryfor this endpointextraOptions: any extra options that were provided to the base query
Additional TS Types
We've added a
TypedQueryStateSelectortype that can be used to pre-type selectors for use withselectFromResult:const typedSelectFromResult: TypedQueryStateSelector< PostsApiResponse, QueryArgument, BaseQueryFunction, SelectedResult > = (state) => ({ posts: state.data?.posts ?? EMPTY_ARRAY }) function PostsList() { const { posts } = useGetPostsQuery(undefined, { selectFromResult: typedSelectFromResult, }) }We've also exported several additional TS types around base queries and tag definitions.
What's Changed
Fix serializeQueryArgs type
Add the
TypedQueryStateSelectorhelper typePass query args to prepareHeaders function
Implement a util function to batch-upsert cache entries
fetchBaseQuery: Expose extraOptions to prepareHeaders
2.2.8
8 Oct 2024Export
QueryExtraOptionsandMutationExtraOptionsFix
mangleErrorsnot preserving different error typesAdd the
TypedUseQueryStateOptionshelper type
This bugfix release fixes a long-standing issue with RTK Query lazy query triggers returning stale data in some cases, fixes an error handling issue in RTK Query, and exports additional TS types.
Changelog
Lazy Query Trigger Handling
We'd had a couple long-standing issues reporting that
const result = await someLazyQueryTrigger()sometimes returned stale data, especially if a mutation had just invalidated that query's tag.We finally got a good repro of this issue and identified it as a mis-written call inside of the middleware that skipped past the necessary handling to activate the correct query status tracking in that scenario. This should now be fixed.
Other Changes
Timeout handling in RTKQ endpoints should now correctly throw a timeout-related error instead of an
AbortError.Base queries now have access to the current
queryCacheKeyvalue so it can be used in deciding query logic.We've exported several more TS types related to query options, as some users have been depending on those even though they previously weren't part of the public API.
What's Changed
Export
QueryExtraOptionsandMutationExtraOptionsFix
mangleErrorsnot preserving different error typesAdd the
TypedUseQueryStateOptionshelper typebaseQuery: Expose queryCacheKey in baseQuery
Fix
AbortErrorbeing triggered incorrectly oncreateApiendpoint timeouttype: Export QueryReturnValue
Call
initiateto refetch queries from middleware
2.2.7
27 Jul 2024This bugfix release fixes issues with "TS type portability" errors, improves build artifact tree shaking behavior, and exports some additional TS types.
Changelog
TS Type Portability
We've had a slew of issues reported around "TS type portability" errors, such as:
The error messages are typically along the lines of:
Type error: The inferred type of
'configureStore'cannot be named without a reference to'@reduxjs/toolkit/node_modules/redux'. This is likely not portable. A type annotation is necessary.@aryaemami59 did some deep investigation and concluded these were due to a mixture of using
interfaceinstead oftypein most places, not pre-bundling our TS typedefs, and not exporting some of the unique symbols we use internally.Arya put together a highly detailed writeup and set of fixes in #4467: Fix: TypeScript Type Portability Issues, and that appears to resolve all of those issues we've seen. Thank you!
Other Changes
Arya also did significant work to improve RTK's treeshaking, tweaking internal definitions to let bundlers better separate out unused code.
We've exported additional types like
UpdateDefinitionsandRetryOptions, per request.listenerMiddleware.withTypes()methods now allow passing in anExtraArgumentgeneric.What's Changed
TypeScript Type Portability Issues
Export UpdateDefinitions from toolkit/query
Adds ExtraArgument to withTypes for listenerMiddleware.
Improve treeshakeability of build artifacts
Export RetryOptions
2.2.6
29 Jun 2024Brings internal
useIsomorphicLayoutEffectusage in line with React Redux in React Native environmentsExports
FetchBaseQueryArgstypeFixes an issue in recent
createEntityAdaptersorting perf improvements that could (in specific cases) cause Immer to throw an error
This bugfix release:
Brings internal
useIsomorphicLayoutEffectusage in line with React Redux in React Native environmentsExports
FetchBaseQueryArgstypeFixes an issue in recent
createEntityAdaptersorting perf improvements that could (in specific cases) cause Immer to throw an error
What's Changed
Fix
useIsomorphicLayoutEffectusage in React Native environmentsAdd export for FetchBaseQueryArgs
Fix Immer current usage when calling addManyMutably more than once
Golf a few pieces of entity adapter code
2.2.5
16 May 2024Fix missed spot where use of
currentmay fail if the value is not a draft
This bugfix release fixes an issue in the recent
createEntityAdaptersorting perf improvements that could (in specific cases) cause Immer to throw an error when trying to read a plain JS value instead of a proxy-wrapped value.What's Changed
Fix missed spot where use of
currentmay fail if the value is not a draft
2.2.4
9 May 2024Update remaining
Contextreferences to match the new nullableContexttypes: Export
GetThunkAPItypeInline the deprecated
React.ReactChildtype
This bugfix release improves sorting performance in
createEntityAdapter, shrinks the code size in matcher utilities, fixes assorted issues with query hooks, and makes several TS tweaks.Changelog
Entity Adapter Sorting Performance
Users reported in #4252 that the sorting performance of
createEntityAdapterseemed abnormally bad - the provided comparison functions were being called far more times than expected.Upon investigation, we had a couple of problems. We were always starting from an array that was in insertion order, not the existing sorted order, and that would always require significant effort to re-sort even if there weren't any actual changes to the sorted results. Also, the sorting checks required frequent access to Immer's Proxy-wrapped values, even in cases where all we needed was the plain state values for comparison purposes.
We've reworked the internal sorting logic to always start from the existing sorted array, do reads against a plain value to avoid the Proxy getter overhead where possible, and optimized inserts into existing sorted arrays. This should significantly speed up sorted entity adapter behavior.
Matcher Code Size Optimization
We've reworked the internals of the thunk-related matchers to deduplicate some of the logic, shaving a few bytes off the final bundle size.
RTK Query Hook Updates
defaultSerializeQueryArgscan now handleBigIntvalues safely.The
isLoadingflag logic was improved to handle errors when a query hook tries to subscribe.TS Updates
create.asyncThunk's types were improved to avoid cases where it might inferany.We've made several internal types changes to work correctly with React 19's upcoming types.
The
retryConditionmethod now receivesunknownas an argument, instead of always assuming the user is usingfetchBaseQuery.Other Changes
The Reselect dep has been bumped to 5.1.0 to match the expected internal usage of
createSelector.withTypes().What's Changed
Update remaining
Contextreferences to match the new nullableContexttypes: Export
GetThunkAPItypeInline the deprecated
React.ReactChildtypeReplace usage of deprecated
JSXglobal namespace withReact.JSXFix
useRefusages to be called with an explicit argument ofundefined.Bump
reselectdependency to 5.1.0 to resolve #4200Improve sorted entity adapter sorting performance
Prevent inference of any in create.asyncThunk
Try out simplifying matcher code
Handle bigint in useQuerySubscription
Ensure hook subscription failures do not reset isLoading state
Improve usability of the
initiate()action for mutations.Make retryCondition receive unknown error, as the user may not be using fetchBaseQuery
2.2.3
31 Mar 2024Update React Redux dependency to v9, and update docs to use
.withTypes
This bugfix release fixes the types for functions that accept a React Context instance to match the changes in React Redux v9.
What's Changed
Update React Redux dependency to v9, and update docs to use
.withTypes
2.2.2
21 Mar 2024Rename TypedUseMutationTrigger to TypedMutationTrigger, and add deprecated alias
Fixed memory leak in rapid hook arg changing
Fix incorrect legacy-esm target
This patch release fixes an incorrect build setting for the
legacy-esmartifacts, and fixes an issue with RTKQ query hooks didn't always remove the cache entries if arguments were changed rapidly.Changes
legacy-esmArtifact TranspilationThe
legacy-esmbuild artifacts are intended for use by Webpack 4. Those were supposed to be transpiled to target"es2017", but were in fact still set to target"esnext"- an oversight during the 2.0 development cycle. This release fixes that setting, so those artifacts are now correctly transpiled.Other Fixes
RTKQ query hooks now handle additional actions around argument changes that should result in cache entries being removed.
Additionally, 2.2.1 contained a fix to an incorrectly named type:
TypedUseMutationTriggeris nowTypedMutationTrigger.What's Changed
Rename TypedUseMutationTrigger to TypedMutationTrigger, and add deprecated alias
Fixed memory leak in rapid hook arg changing
Fix incorrect legacy-esm target
2.2.1
14 Feb 20242.2.0
12 Feb 2024Adds a second parameter to
entityAdapter.getInitialState(additionalProps, entities)to allow prefilling stateAllows initialising
combineSliceswith no static reducersAllows a new
'throw'value foroverrideExistingininjectEndpoints, which throws an error if a definition is injected with a name which is already used
This minor release:
Adds a second parameter to
entityAdapter.getInitialState(additionalProps, entities)to allow prefilling stateEquivalent to
entityAdapter.setAll(entityAdapter.getInitialState(additionalProps), entities)First parameter can be
undefinedif no additional properties are desired
Allows initialising
combineSliceswith no static reducersPreviously
const combinedReducer = combineSlices().withLazyLoadedSlices<LazyLoadedSlices>()would have thrown an errorNow returns a "no-op" reducer that just returns an empty object until first reducer injected
Allows a new
'throw'value foroverrideExistingininjectEndpoints, which throws an error if a definition is injected with a name which is already usedExports more type helpers for RTKQ hook and trigger types
Exports types related to overriding result types in
enhanceEndpointsFixes state inference for injected slices when undeclared (i.e. not in
LazyLoadedSlices)Adds a
action.meta.arg.isPrefetchvalue to query thunk actions when prefetched
What's Changed
Revamp type tests setup
Bump Prettier and Prettier related packages
Fix codemods to work with TypeScript 4.7+
Export types related to overriding the result types
Migrate type tests to Vitest
Fix RetryOptions type test by flipping order
Format all files
skipPollingIfUnfocused added to polling overview and query options
Temporarily revert node-fetch bump until ESM issues are sorted
Contributing Markdown Refresh
Investigate re-adding size limit action
Add size limit imports for more commonly used RTK exports
Pure some things
Cut back on suffixes tested for size
Incorrect builder method referenced
Rename
cli.jstocli.mjsFix wrong state for injected slices when not declared via
withLazyLoadedSlices: add missing setOne and setMany signature
Corrected the "Observing cache behaviour" example point no.4
Add isPrefetch property in query action
Allow initialising combined slice reducer with no static slices
Create more Typed wrappers for RTKQ hook types
Proposal fix for axios base query types
Provide 'throw' option for
overrideExistingUse vite-tsconfig-paths to make path aliasing easier
Remove trailing commas in
tsconfig.jsonfiles of all CodesandBox examplesAdd second parameter to getInitialState to prefill entities
2.1.0
24 Jan 2024Adds withTypes methods to
listenerMiddlewareandcreateDraftSafeSelectorAdds a
skipPollingIfUnfocusedoption to RTK QueryAdds the ability to customise the
createSelectorinstance used by RTK Query
This minor release:
Adds withTypes methods to
listenerMiddlewareandcreateDraftSafeSelectorAdds a
skipPollingIfUnfocusedoption to RTK QueryAdds the ability to customise the
createSelectorinstance used by RTK QueryReworks slice selector logic to avoid depending on
thisvalueFixes the order and inference of
create.asyncThunktype parametersFixes requirements for meta fields returned from
queryFnsMarks promises that will never reject as safe, in preparation for #7008
What's Changed
Update docs to avoid circular type
Copy "Migrating to Modern Redux" and "RTK is Redux" docs from core site
Fix store path rtk-query pokemon api tutorial
Tweak RTKQ without hooks section, and add note regarding memoization
Add section regarding overriding deps
Add section re: RR custom context typing change
Add Expo demo app to CI workflow
Preserve nullable store state type by avoiding intersection with {}
Upgrade version of "graphql-request"
Update RTK peerDependency
Improve selectFromResult memoization
Rename "createSliceWithThunks" and "createThunkSlice" to "createAppSlice"
Bump Vitest to latest version
Fix inference and order of async thunk generics
Fix markdown links on Next.js page
Introduce pre-typed listener middleware via
listenerMiddleware.withTypes<RootState, AppDispatch>()methodAdd
.withTypestocreateDraftSafeSelectorTask/remove all settled
Allow customising createSelector instance used by RTKQ
cacheLifecycle fix for fixedCacheKey in mutationThunk
Avoid relying on
thisin createSlicedocumentation: Fix grammar in redux and redux toolkit comparison
Update
tsconfig.typetests.jsonto include all TS filesRemove abort event listner for AbortController
fix/kitchen-sink-isAuthenticated: Fixed isAuthenticated state change on login fullfilled
Fix
composeWithDevToolsspyRequire queryFn meta to match base query
Added 'SafePromise' branded Promises for createAsyncThunk
Add React Native demo app to CI workflow
Add missing reducer field to ConfigureStoreOptions docs
Option for queries to pause polling when unfocused
2.0.1
4 Dec 20232.0.0
4 Dec 2023- removed
Removes the deprecated object syntax from
createSliceandcreateReducer - removed
Removes other deprecated options
- removed
Remove remaining deprecated exports
This major release :
- removed
Removes the deprecated object syntax from
createSliceandcreateReducer - removed
Removes other deprecated options
Updates the
middlewareandenhancersoptions ofconfigureStoreto require callbacksUpdates the packaging for better ESM/CJS compatibility and modernizes the build output
Includes all changes to Redux core 5.0, Reselect 5.0, and Redux Thunk 3.0
Updates RTKQ default subscription behavior
Adds a new
combineSlicesmethod with support for lazy-loading slice reducersAdds a new "dynamic middleware" middleware with support for adding middleware at runtime
Adds a new callback syntax to
createSlice.reducers, with optional support for defining thunks inside ofcreateSliceAdds the
autoBatchEnhancertoconfigureStoreby defaultHas many additional TS tweaks and improvements
This release has breaking changes. (Note: v2.0.1 was released with a couple hotfixes for Reselect and Redux Thunk right as this was being finalized.)
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.
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
Changelog
Object syntax for
createSlice.extraReducersandcreateReducerremovedRTK's
createReducerAPI was originally designed to accept a lookup table of action type strings to case reducers, like{ "ADD_TODO": (state, action) => {} }. We later added the "builder callback" form to allow more flexibility in adding "matchers" and a default handler, and did the same forcreateSlice.extraReducers.We have removed the "object" form for both
createReducerandcreateSlice.extraReducersin RTK 2.0, as the builder callback form is effectively the same number of lines of code, and works much better with TypeScript.As an example, this:
const todoAdded = createAction('todos/todoAdded') createReducer(initialState, { [todoAdded]: (state, action) => {}, }) createSlice({ name, initialState, reducers: { /* case reducers here */ }, extraReducers: { [todoAdded]: (state, action) => {}, }, })should be migrated to:
createReducer(initialState, (builder) => { builder.addCase(todoAdded, (state, action) => {}) }) createSlice({ name, initialState, reducers: { /* case reducers here */ }, extraReducers: (builder) => { builder.addCase(todoAdded, (state, action) => {}) }, })Codemods
To simplify upgrading codebases, we've published a set of codemods that will automatically transform the deprecated "object" syntax into the equivalent "builder" syntax.
The codemods package is available on NPM as
@reduxjs/rtk-codemods. More details are available here.To run the codemods against your codebase, run
npx @reduxjs/rtk-codemods <TRANSFORM NAME> path/of/files/ or/some**/*glob.js.Examples:
npx @reduxjs/rtk-codemods createReducerBuilder ./src npx @reduxjs/rtk-codemods createSliceBuilder ./packages/my-app/**/*.ts
We also recommend re-running Prettier on the codebase before committing the changes.
These codemods should work, but we would greatly appreciate feedback from more real-world codebases!
configureStoreOptions ChangesconfigureStore.middlewaremust be a callbackSince the beginning,
configureStorehas accepted a direct array value as themiddlewareoption. However, providing an array directly preventsconfigureStorefrom callinggetDefaultMiddleware(). So,middleware: [myMiddleware]means there is no thunk middleware added (or any of the dev-mode checks).This is a footgun, and we've had numerous users accidentally do this and cause their apps to fail because the default middleware never got configured.
As a result, we've now made the
middlewareonly accept the callback form. If for some reason you still want to replace all of the built-in middleware, do so by returning an array from the callback:const store = configureStore({ reducer, middleware: (getDefaultMiddleware) => { // WARNING: this means that _none_ of the default middleware are added! return [myMiddleware] // or for TS users, use: // return new Tuple(myMiddleware) }, })But note that we consistently recommend not replacing the default middleware entirely, and that you should use
return getDefaultMiddleware().concat(myMiddleware).configureStore.enhancersmust be a callbackSimilarly to
configureStore.middleware, theenhancersfield must also be a callback, for the same reasons.The callback will receive a
getDefaultEnhancersfunction that can be used to customise the batching enhancer that's now included by default.For example:
const store = configureStore({ reducer, enhancers: (getDefaultEnhancers) => { return getDefaultEnhancers({ autoBatch: { type: 'tick' }, }).concat(myEnhancer) }, })It's important to note that the result of
getDefaultEnhancerswill also contain the middleware enhancer created with any configured/default middleware. To help prevent mistakes,configureStorewill log an error to console if middleware was provided and the middleware enhancer wasn't included in the callback result.const store = configureStore({ reducer, enhancers: (getDefaultEnhancers) => { return [myEnhancer] // we've lost the middleware here // instead: return getDefaultEnhancers().concat(myEnhancer) }, })Also, note that if you supply the
enhancersfield, it must come after themiddlewarefield in order for TS inference to work properly.Standalone
getDefaultMiddlewareandgetTyperemovedThe standalone version of
getDefaultMiddlewarehas been deprecated since v1.6.1, and has now been removed. Use the function passed to themiddlewarecallback instead, which has the correct types.We have also removed the
getTypeexport, which was used to extract a type string from action creators made withcreateAction. Instead, use the static propertyactionCreator.type.RTK Query behaviour changes
We've had a number of reports where RTK Query had issues around usage of
dispatch(endpoint.initiate(arg, {subscription: false})). There were also reports that multiple triggered lazy queries were resolving the promises at the wrong time. Both of these had the same underlying issue, which was that RTKQ wasn't tracking cache entries in these cases (intentionally). We've reworked the logic to always track cache entries (and remove them as needed), which should resolve those behavior issues.We also have had issues raised about trying to run multiple mutations in a row and how tag invalidation behaves. RTKQ now has internal logic to delay tag invalidation briefly, to allow multiple invalidations to get handled together. This is controlled by a new
invalidationBehavior: 'immediate' | 'delayed'flag oncreateApi. The new default behavior is'delayed'. Set it to'immediate'to revert to the behavior in RTK 1.9.In RTK 1.9, we reworked RTK Query's internals to keep most of the subscription status inside the RTKQ middleware. The values are still synced to the Redux store state, but this is primarily for display by the Redux DevTools "RTK Query" panel. Related to the cache entry changes above, we've optimized how often those values get synced to the Redux state for perf.
ESM/CJS Package Compatibility
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/redux-toolkit.modern.mjs. Most build tools should pick this up. There's also a CJS artifact, and a second copy of the ESM file namedredux-toolkit.legacy-esm.jsto support Webpack 4 (which does not recognize theexportsfield inpackage.json). Additionally, all of the build artifacts now live under./dist/in the published package.Modernized Build Output
We now publish modern JS syntax targeting ES2020, including optional chaining, object spread, and other modern syntax. If you need to target older browsers, please transpile the packages yourself (or use the
legacy-esmbuild artifact for ES2017).Build Tooling
We're now building the package using github.com/egoist/tsup. We also now include sourcemaps for the ESM and CJS artifacts.
Dropping UMD Builds
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
redux-toolkit.browser.mjsfile 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!
Dependency Updates
Redux Libraries
RTK now depends on Redux core 5.0, Reselect 5.0, and Redux Thunk 3.0. See the linked release notes for those libraries, as each of them has additional breaking changes. The "Migrating to RTK 2.0 and Redux 5.0" docs page also covers the combined changes in one page
Immer 10
RTK now also depends on Immer 10.0, which has several major improvements and updates:
We've also removed the prior call to automatically enable the Immer ES5 fallback mode any time RTK was loaded.
Other Changes
Bundle Size Optimizations
Redux 4.1.0 optimized its bundle size by extracting error message strings out of production builds, based on React's approach. We've applied the same technique to RTK. This saves about 1000 bytes from prod bundles (actual benefits will depend on which imports are being used).
We also noted that ESBuild does not deduplicate imports when it bundles source files, and this was causing RTK Query's bundle to contain a dozen references to
import { } from "@reduxjs/toolkit", including some of the same methods. Manually deduplicating those saves about 600 bytes off the production RTKQ artifact.reactHooksModulecustom hook configurationPreviously, custom versions of React Redux's hooks (
useSelector,useDispatch, anduseStore) could be passed separately toreactHooksModule, usually to enable using a different context to the defaultReactReduxContext.In practicality, the react hooks module needs all three of these hooks to be provided, and it became an easy mistake to only pass
useSelectoranduseDispatch, withoutuseStore.The module has now moved all three of these under the same configuration key, and will check that all three are provided if the key is present.
// previously const customCreateApi = buildCreateApi( coreModule(), reactHooksModule({ useDispatch: createDispatchHook(MyContext), useSelector: createSelectorHook(MyContext), useStore: createStoreHook(MyContext), }) ) // now const customCreateApi = buildCreateApi( coreModule(), reactHooksModule({ hooks: { useDispatch: createDispatchHook(MyContext), useSelector: createSelectorHook(MyContext), useStore: createStoreHook(MyContext), }, }) )Deprecated Options Removed
Several other options were previously marked as deprecated, and have been removed. We've also removed polyfills like the
AbortControllerpolyfill.TypeScript Changes
configureStorefield order formiddlewaremattersIf you are passing both the
middlewareandenhancersfields toconfigureStore, themiddlewarefield must come first in order for internal TS inference to work properly.Non-default middleware/enhancers must use
TupleWe've seen many cases where users passing the
middlewareparameter to configureStore have tried spreading the array returned bygetDefaultMiddleware(), or passed an alternate plain array. This unfortunately loses the exact TS types from the individual middleware, and often causes TS problems down the road (such asdispatchbeing typed asDispatch<AnyAction>and not knowing about thunks).getDefaultMiddleware()already used an internalMiddlewareArrayclass, anArraysubclass that had strongly typed.concat/prepend()methods to correctly capture and retain the middleware types.We've renamed that type to
Tuple, andconfigureStore's TS types now require that you must useTupleif you want to pass your own array of middleware:import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => new Tuple(additionalMiddleware, logger), })(Note that this has no effect if you're using RTK with plain JS, and you could still pass a plain array here.)
This same restriction applies to the
enhancersfield.Entity adapter type updates
createEntityAdapternow has anIdgeneric argument, which will be used to strongly type the item IDs anywhere those are exposed. Previously, the ID field type was alwaysstring | number. TS will now try to infer the exact type from either the.idfield of your entity type, or theselectIdreturn type. You could also fall back to passing that generic type directly. If you use theEntityState<Data, Id>type directly, you must supply both generic arguments!The
.entitieslookup table is now defined to use a standard TSRecord<Id, MyEntityType>, which assumes that each item lookup exists by default. Previously, it used aDictionary<MyEntityType>type, which assumed the result wasMyEntityType | undefined. TheDictionarytype has been removed.If you prefer to assume that the lookups might be undefined, use TypeScript's
noUncheckedIndexedAccessconfiguration option to control that.New Features
These features are new in Redux Toolkit 2.0, and help cover additional use cases that we've seen users ask for in the ecosystem.
combineSlicesAPI with slice reducer injection for code-splittingThe Redux core has always included
combineReducers, which takes an object full of "slice reducer" functions and generates a reducer that calls those slice reducers. RTK'screateSlicegenerates slice reducers + associated action creators, and we've taught the pattern of exporting individual action creators as named exports and the slice reducer as a default export. Meanwhile, we've never had official support for lazy-loading reducers, although we've had sample code for some "reducer injection" patterns in our docs.This release includes a new
combineSlicesAPI that is designed to enable lazy-loading of reducers at runtime. It accepts individual slices or an object full of slices as arguments, and automatically callscombineReducersusing thesliceObject.namefield as the key for each state field. The generated reducer function has an additional.inject()method attached that can be used to dynamically inject additional slices at runtime. It also includes a.withLazyLoadedSlices()method that can be used to generate TS types for reducers that will be added later. See #2776 for the original discussion around this idea.For now, we are not building this into
configureStore, so you'll need to callconst rootReducer = combineSlices(.....)yourself and pass that toconfigureStore({reducer: rootReducer}).Basic usage: a mixture of slices and standalone reducers passed to
combineSlicesconst stringSlice = createSlice({ name: 'string', initialState: '', reducers: {}, }) const numberSlice = createSlice({ name: 'number', initialState: 0, reducers: {}, }) const booleanReducer = createReducer(false, () => {}) const api = createApi(/* */) const combinedReducer = combineSlices( stringSlice, { num: numberSlice.reducer, boolean: booleanReducer, }, api ) expect(combinedReducer(undefined, dummyAction())).toEqual({ string: stringSlice.getInitialState(), num: numberSlice.getInitialState(), boolean: booleanReducer.getInitialState(), api: api.reducer.getInitialState(), })Basic slice reducer injection
// Create a reducer with a TS type that knows `numberSlice` will be injected const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices< WithSlice<typeof numberSlice> >() // `state.number` doesn't exist initially expect(combinedReducer(undefined, dummyAction()).number).toBe(undefined) // Create a version of the reducer with `numberSlice` injected (mainly useful for types) const injectedReducer = combinedReducer.inject(numberSlice) // `state.number` now exists, and injectedReducer's type no longer marks it as optional expect(injectedReducer(undefined, dummyAction()).number).toBe( numberSlice.getInitialState() ) // original reducer has also been changed (type is still optional) expect(combinedReducer(undefined, dummyAction()).number).toBe( numberSlice.getInitialState() )selectorsfield increateSliceThe existing
createSliceAPI now has support for definingselectorsdirectly as part of the slice. By default, these will be generated with the assumption that the slice is mounted in the root state usingslice.nameas the field, such asname: "todos"->rootState.todos. Additionally, there's now aslice.selectSlicemethod that does that default root state lookup.You can call
sliceObject.getSelectors(selectSliceState)to generate the selectors with an alternate location, similar to howentityAdapter.getSelectors()works.const slice = createSlice({ name: 'counter', initialState: 42, reducers: {}, selectors: { selectSlice: (state) => state, selectMultiple: (state, multiplier: number) => state * multiplier, }, }) // Basic usage const testState = { [slice.name]: slice.getInitialState(), } const { selectSlice, selectMultiple } = slice.selectors expect(selectSlice(testState)).toBe(slice.getInitialState()) expect(selectMultiple(testState, 2)).toBe(slice.getInitialState() * 2) // Usage with the slice reducer mounted under a different key const customState = { number: slice.getInitialState(), } const { selectSlice, selectMultiple } = slice.getSelectors( (state: typeof customState) => state.number ) expect(selectSlice(customState)).toBe(slice.getInitialState()) expect(selectMultiple(customState, 2)).toBe(slice.getInitialState() * 2)createSlice.reducerscallback syntax and thunk supportOne of the oldest feature requests we've had is the ability to declare thunks directly inside of
createSlice. Until now, you've always had to declare them separately, give the thunk a string action prefix, and handle the actions viacreateSlice.extraReducers:// Declare the thunk separately const fetchUserById = createAsyncThunk( 'users/fetchByIdStatus', async (userId: number, thunkAPI) => { const response = await userAPI.fetchById(userId) return response.data } ) const usersSlice = createSlice({ name: 'users', initialState, reducers: { // standard reducer logic, with auto-generated action types per reducer }, extraReducers: (builder) => { // Add reducers for additional action types here, and handle loading state as needed builder.addCase(fetchUserById.fulfilled, (state, action) => { state.entities.push(action.payload) }) }, })Many users have told us that this separation feels awkward.
We've wanted to include a way to define thunks directly inside of
createSlice, and have played around with various prototypes. There were always two major blocking issues, and a secondary concern:It wasn't clear what the syntax for declaring a thunk inside should look like.
Thunks have access to
getStateanddispatch, but theRootStateandAppDispatchtypes are normally inferred from the store, which in turn infers it from the slice state types. Declaring thunks insidecreateSlicewould cause circular type inference errors, as the store needs the slice types but the slice needs the store types. We weren't willing to ship an API that would work okay for our JS users but not for our TS users, especially since we want people to use TS with RTK.You can't do synchronous conditional imports in ES modules, and there's no good way to make the
createAsyncThunkimport optional. EithercreateSlicealways depends on it (and adds that to the bundle size), or it can't usecreateAsyncThunkat all.
We've settled on these compromises:
In order to create async thunks with
createSlice, you specifically need to set up a custom version ofcreateSlicethat has access tocreateAsyncThunk.You can declare thunks inside of
createSlice.reducers, by using a "creator callback" syntax for thereducersfield that is similar to thebuildcallback syntax in RTK Query'screateApi(using typed functions to create fields in an object). Doing this does look a bit different than the existing "object" syntax for thereducersfield, but is still fairly similar.You can customize some of the types for thunks inside of
createSlice, but you cannot customize thestateordispatchtypes. If those are needed, you can manually do anascast, likegetState() as RootState.
In practice, we hope these are reasonable tradeoffs. Creating thunks inside of
createSlicehas been widely asked for, so we think it's an API that will see usage. If the TS customization options are a limitation, you can still declare thunks outside ofcreateSliceas always, and most async thunks don't needdispatchorgetState- they just fetch data and return. And finally, setting up a customcreateSliceallows you to opt intocreateAsyncThunkbeing included in your bundle size (though it may already be included if used directly or as part of RTK Query - in either of these cases there's no additional bundle size).Here's what the new callback syntax looks like:
const createSliceWithThunks = buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator }, }) const todosSlice = createSliceWithThunks({ name: 'todos', initialState: { loading: false, todos: [], error: null, } as TodoState, reducers: (create) => ({ // A normal "case reducer", same as always deleteTodo: create.reducer((state, action: PayloadAction<number>) => { state.todos.splice(action.payload, 1) }), // A case reducer with a "prepare callback" to customize the action addTodo: create.preparedReducer( (text: string) => { const id = nanoid() return { payload: { id, text } } }, // action type is inferred from prepare callback (state, action) => { state.todos.push(action.payload) } ), // An async thunk fetchTodo: create.asyncThunk( // Async payload function as the first argument async (id: string, thunkApi) => { const res = await fetch(`myApi/todos?id=${id}`) return (await res.json()) as Item }, // An object containing `{pending?, rejected?, fulfilled?, settled?, options?}` second { pending: (state) => { state.loading = true }, rejected: (state, action) => { state.error = action.payload ?? action.error }, fulfilled: (state, action) => { state.todos.push(action.payload) }, // settled is called for both rejected and fulfilled actions settled: (state, action) => { state.loading = false }, } ), }), }) // `addTodo` and `deleteTodo` are normal action creators. // `fetchTodo` is the async thunk export const { addTodo, deleteTodo, fetchTodo } = todosSlice.actionsCodemod
Using the new callback syntax is entirely optional (the object syntax is still standard), but an existing slice would need to be converted before it can take advantage of the new capabilities this syntax provides. To make this easier, a codemod is provided.
npx @reduxjs/rtk-codemods createSliceReducerBuilder ./src/features/todos/slice.ts
"Dynamic middleware" middleware
A Redux store's middleware pipeline is fixed at store creation time and can't be changed later. We have seen ecosystem libraries that tried to allow dynamically adding and removing middleware, potentially useful for things like code splitting.
This is a relatively niche use case, but we've built our own version of a "dynamic middleware" middleware. Add it to the Redux store at setup time, and it lets you add middleware later at runtime. It also comes with a React hook integration that will automatically add a middleware to the store and return the updated dispatch method..
import { createDynamicMiddleware, configureStore } from '@reduxjs/toolkit' const dynamicMiddleware = createDynamicMiddleware() const store = configureStore({ reducer: { todos: todosReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(dynamicMiddleware.middleware), }) // later dynamicMiddleware.addMiddleware(someOtherMiddleware)configureStoreaddsautoBatchEnhancerby defaultIn v1.9.0, we added a new
autoBatchEnhancerthat delays notifying subscribers briefly when multiple "low-priority" actions are dispatched in a row. This improves perf, as UI updates are typically the most expensive part of the update process. RTK Query marks most of its own internal actions as "low-pri" by default, but you have to have theautoBatchEnhanceradded to the store to benefit from that.We've updated
configureStoreto add theautoBatchEnhancerto the store setup by default, so that users can benefit from the improved perf without needing to manually tweak the store config themselves.entityAdapter.getSelectorsaccepts acreateSelectorfunctionentityAdapter.getSelectors()now accepts an options object as its second argument. This allows you to pass in your own preferredcreateSelectormethod, which will be used to memoize the generated selectors. This could be useful if you want to use one of Reselect's new alternate memoizers, or some other memoization library with an equivalent signature.Next.js Setup Guide
We now have a docs page that covers how to set up Redux properly with Next.js. We've seen a lot of questions around using Redux, Next, and the App Router together, and this guide should help provide advice.
(At this time, the Next.js
with-reduxexample is still showing outdated patterns - we're going to file a PR shortly to update that to match our docs guide.)What's Changed
Remove legacy object syntax for reducers
Upgrade build tooling
Migrate the RTK package to be full ESM
Migrate RTK test suite from Jest to Vitest
Fix type errors after upgrading to Redux 5 alpha
Bump Redux dep to 5.0.0-alpha.2
Test published artifacts in CI
Merge RTK CI examples into
v2.0-integrationAdd
arethetypeswrongautomated CLI checkUpdate tip regarding overrideExisting to match actual behaviour
Add
attwCLI option to treat problems as non-errorsRework build setup and hopefully fix ESM compat issues
Switch build setup from a custom ESBuild+TS script to
tsupUse original instead of immer draft for perf
Enable enhanceEndpoints.transformResponse to override ResultType
Fix global
responseHandlerbeing used infetchBaseQueryReset internalState.currentSubscriptions on
resetApiStateBump deps and mark
subscriptionUpdatedas autobatchedRedux 5alpha5
Add isAction helper function, and ensure listener middleware only runs for actions
Allow inference of enhancer state extensions, and fix inference when using callback form
combineSlices implementation
Bump Immer to 10.0 final
Allow partial preloaded state for combined slice reducer and update devDeps
Ensure it's only possible to pass all or none of the hooks to reactHooksModule
- removed
Remove remaining deprecated exports
Create action creator middleware
Restore query status types
Implement auto fork joining
types: Make it easier to wrap createAsyncThunk
Fixed Stackoverflow bug if children prop is a ref to root/parent object
Add getDefaultEnhancers callback, and add autoBatchEnhancer to defaults.
Update to Redux alpha 6
Remove toString override from action creators, in favour of explicit .type field.
Add "creator callback" syntax to slice reducer field, to allow for async thunk creation
API for adding middleware dynamically
Add Id type parameter in createEntityAdapter
Require usage of Tuple in TS
Require that enhancers is a callback
Fix invalid createEntityAdapter call
Use Record<EntityId, T> instead of Dictionary<T>
Fix inference for reducers without actions specified
Prefer UnknownAction and Action to AnyAction
Allow passing a
createSelectorinstance to adapter.getSelectorsAdd required type parameter to EntityState in docs
Fix syntax for users with noPropertyAccessFromIndexSignature enabled
Fix TransformedResponse type to unwrap promise
Fix multiple TS build and packaging issues
Default export of the module has or is using private name type error when using latest alpha/beta
RTK Query: Resolve type portability issues
RTKQuery React: Resolved declare module pathing for Node16
- removed
2448: Remove abort controller polyfill
Throw error when type is empty in builder.addCase
Updated type references to resolve portable types issue
Add option to update provided tags
Add selectCachedArgsForQuery util
Remove Request.clone() usage in fetchBaseQuery
Try working around TS 4.1 mismatch
Optimize bundles for RTK 2.0
Work around known TS bug with type inference
- removed
Remove middleware array option
Warn for and migrate old options and require all hooks
Feature/entity adapter draftable entity state
Write create callback codemod
Copy of "Work around known TS bug with type inference #3761"
Rework named hooks type (v1.9)
Rewrite HooksWithUniqueNames type (v2.0)
Adds the option of generating real TS enums instead of string unions
Adjust snake case regex
Update error message: name of action type
Created listenerApi.cancel()
Add
listenerApi.throwIfCancelled()Add settled matcher for createAsyncThunk
Rewrite RTKQ internal subscription lookups and subscription syncing
Keep subscription on data while query is running
Stop re-exporting
autotrackMemoizeDelayed tag invalidations
Output selector fields are currently missing in selector functions created using
createDraftSafeSelector.createDynamicMiddleware bike shedding
Allow passing selector creators with different memoize options to getSelectors
Add selectSlice to slice instance
Throw an error if ApiProvider is nested inside a normal Provider.
Update store configuration using configureStore in createSlice.mdx
Create standardised methods of modifying reducer handler context.
Require calling buildCreateSlice to use create.asyncThunk
Restore the toString override, but keep it out of the docs.
Selector housekeeping - emplace and unwrapped
Update build tooling for 2.0
Export EndpointBuilder
Update to Redux core v5 rc1 and replace isAction/isPlainObject with core counterparts
Drop "ESM modern dev" build and rename "ESM prod" to "browser"
Fix isFSA import and avoid unnecessary type param
Avoid passing an identity function to createSelector
- 1.x
1.9.7
4 Oct 2023Copy of "Work around known TS bug with type inference #3761"
Rework named hooks type (v1.9)
This bugfix release rewrites the RTKQ hook TS types to significantly improve TS perf.
Changelog
RTKQ TS Perf
A number of users had reported that Intellisense for RTKQ API objects was extremely slow (multiple seconds) - see discussion in #3214 . We did some perf investigation on user-provided examples, and concluded that the biggest factor to slow RTKQ TS perf was the calculation of hook names like
useGetPokemonQuery, which was generating a large TS union of types.We've rewritten that hook names type calculation to use mapped types and a couple of intersections. In a specific user-provided stress test repo, it dropped TS calculation time by 60% (2600ms to 1000ms).
There's more potential work we can do to improve things, but this seems like a major perf improvement worth shipping now.
What's Changed
Copy of "Work around known TS bug with type inference #3761"
Rework named hooks type (v1.9)
1.9.6
24 Sep 2023Create action creator middleware
Implement auto fork joining
types: Make it easier to wrap createAsyncThunk
This bugfix release adds a new dev-mode middleware to catch accidentally dispatching an action creator, adds a new listener middleware option around waiting for forks, adds a new option to update provided tags when
updateQueryDatais used, reworks internal types to better handle uses with TS declaration output, and fixes a variety of small issues.Changelog
Action Creator Dev Check Middleware
RTK already includes dev-mode middleware that check for the common mistakes of accidentally mutating state and putting non-serializable values into state or actions.
Over the years we've also seen a semi-frequent error where users accidentally pass an action creator reference to
dispatch, instead of calling it and dispatching the action it returns.We've added another dev-mode middleware that specifically catches this error and warns about it.
Additional Options
The listener middleware's
listenerApi.fork()method now has an optionalautoJoinflag that can be used to keep the effect from finishing until all active forked tasks have completed.updateQueryDatanow has anupdateProvidedTagsoption that will force a recalculation of that endpoint's provided tags. It currently defaults tofalse, and we'll likely turn that totruein the next major.Other Fixes
The
builder.addCasemethod now throws an error if atypestring is empty.fetchBaseQuerynow uses an alternate method to clone the originalRequestin order to work around an obscure Chrome bug.The immutability middleware logic was tweaked to avoid a potential stack overflow.
Types Changes
The internal type imports have been reworked to try to fix "type portability" issues when used in combination with TS declaration outputs.
A couple additional types were exported to help with wrapping
createAsyncThunk.What's Changed
Create action creator middleware
Implement auto fork joining
types: Make it easier to wrap createAsyncThunk
Fixed Stackoverflow bug if children prop is a ref to root/parent object
Fix TransformedResponse type to unwrap promise
Throw error when type is empty in builder.addCase
Updated type references to resolve portable types issue
Add option to update provided tags
Remove Request.clone() usage in fetchBaseQuery
Try working around TS 4.1 mismatch
1.9.5
18 Apr 2023Update to latest remark-typescript-tools
Add isAction helper function, and ensure listener middleware only runs for actions
Allow inference of enhancer state extensions, and fix inference when using callback form
This bugfix release includes notable improvements to TS type inference when using the
enhancersoption inconfigureStore, and updates the listener middleware to only check predicates if the dispatched value is truly an action object.What's Changed
Update to latest remark-typescript-tools
Add isAction helper function, and ensure listener middleware only runs for actions
Allow inference of enhancer state extensions, and fix inference when using callback form
1.9.4
17 Apr 2023Test published artifacts in CI
Use Git revision in version and add Node CI examples
Add
arethetypeswrongautomated CLI check
This bugfix release includes tweaks to RTKQ options handling, tweaks for perf updates, dependency updates, and updates to our CI tooling.
Also, please check out our ongoing RTK 2.0 alpha releases! They have significant improvements to bundle size, ESM/CJS compatibility, TS typings, and reducer update performance. We're looking for real-world feedback on behavior, performance, and any issues you might run into.
Changelog
RTK Query Options Updates
Passing
transformResponseas part ofenhanceEndpointscan now override the TS type of the original data.fetchBaseQuerynow properly checks for a globalresponseHandleroption.Performance and Internals
RTK Query now uses Immer's
original()to do comparisons inside ofcopyWithStructuralSharing, which should significantly speed up performance when applying changes from re-fetched data.RTKQ's internal
subscriptionUpdatedaction is now marked as batchable.We've updated dependencies to Immer 9.0.21, Reselect 4.1.8, and Redux 4.2.1.
CI Updates
We've added a suite of example apps built with different frameworks such as CRA 4, CRA 5, Next, and Vite, as well as examples that check for compatibility in Node with CJS and ESM modes and with various TS module resolution modes.
What's Changed
Test published artifacts in CI
Use Git revision in version and add Node CI examples
Add
arethetypeswrongautomated CLI checkAdd
attwCLI option to treat problems as non-errorsUse original instead of immer draft for perf
Enable enhanceEndpoints.transformResponse to override ResultType
Fix global
responseHandlerbeing used infetchBaseQueryReset internalState.currentSubscriptions on
resetApiStateBump deps and mark
subscriptionUpdatedas autobatched
1.9.3
21 Feb 2023Revert "clear data on skip" back to its original behavior
Use a WeakMap cache for query arg serialization for perf
This release fixes a couple issues with the
skip/skipTokenoptions for query hooks, and makes a small perf tweak to serializing query args.Changelog
Skip Behavior
We made a change in v1.9.0 that tried to make some skip behavior more consistent, including clearing out the cached data. However, we had overlooked that our own docs actually said "skipping a query will keep the cached data", and several users pointed this out as they'd been relying on that behavior.
We've reverted that change. Now, setting
{skip: true}orskipTokenfor a query with existing results will keep thedatavalue (reflecting the last successful query), butcurrentDatawill be undefined (reflecting the current settings).We also identified and fixed an issue that could cause subscription entries to leak under a specific combination of timing and settings changes.
Query Arg Serialization Perf
RTKQ relies on serializing query arguments to serve as the cache keys, with the default using
JSON.stringify()+ some logic for sorting keys. There was a report that in some apps, large query arg objects could take a while to stringify and this was being done repeatedly. We've added aWeakMap-based cache for query args to avoid re-serializing existing arg values.What's Changed
Revert "clear data on skip" back to its original behavior
Use a WeakMap cache for query arg serialization for perf
1.9.2
28 Jan 2023Fix CaseReducer to infer from argument, not return value
fetchBaseQuery | Add jsonReplacer param
Support RegExp in ignoredPaths/ignoredActionPaths
This bugfix release fixes a memory leak in
createListenerMiddleware, optimizes performance insideserializableMiddleware, adds new options forfetchBaseQuery, adds support for pathRegExpexclusions inserializableMiddlewareandimmutabilityMiddleware, and improves some TS types.Changelog
Bug Fixes
createListenerMiddlewarehad a memory leak that turned out to be due to use ofPromise.race(). We've restructured the logic to fix that.fetchBaseQuerynow correctly combines global options with endpoint / default options in all cases.New Options
fetchBaseQuerynow supports ajsonReplaceroption that will be used when processing JSON.Both dev check middleware now support regular expressions in the
ignoredPathsarray in addition to strings. This adds extra flexibility in skipping certain fields.TS Changes
The
CaseReducertype was sometimes incorrectly inferring its return type in rare cases. That's been fixed.The
isAnyOf/isAllOfmatcher function TS types have been tweaked to not require an individual first parameter. This allows spreading arrays of matchers as arguments, likeconst isLoading = isAnyOf(...interestingPendingThunksArray).Other Changes
The
serializableMiddlewarenow uses aWeakSetif available to cache values it's seen. This should significantly speed up checks against large state values in development builds.What's Changed
Fix CaseReducer to infer from argument, not return value
fetchBaseQuery | Add jsonReplacer param
Support RegExp in ignoredPaths/ignoredActionPaths
types: Export
ThunkWithReturnValueinterfaceRemove unnecessary fetchBaseQuery defaults
Make isAnyOf friendly for mapped matchers, but making argument optional
raceWithSignalmethod instead ofPromise.raceFix lint problems and enable linting on CI
Add caching to serializableStateInvariantMiddleware
Allow TS isolatedModules flag to be set for safer transpilation
1.9.1
30 Nov 2022Fix createAsyncThunk.withTypes
Update timer polyfills to work in more environments
Retry now checks whether potential retry counts are undefined, rather than boolean, in order to avoid filtering out 0's
This bugfix release fixes assorted issues that were reported with RTK 1.9.0, and adds a few additional requested tweaks and improvements.
Changelog
Fixes
The
createAsyncThunk.withTypesfunction was fully broken (it type-checked correctly, but pointed to the wrong function due to a name shadowing issue). That now works correctly.The
maxRetriesoption for RTKQ was inadvertently filtering out0values, and those are now accepted.fulfillWithValuehad incorrect types that made it appear as if the data was nested an additional level deeper. The types are now correct.The
ActionCreatorWithoutPayloadtype was tweaked to force an error when an action creator is accidentally called with an argument, which happens in cases likeonClick={todoAdded}. This avoids accidentally passing values like React event objects as the payload.Timer handling for
batchActionsandautoBatchEnhancernow works in more JS runtime environments.Other Changes
The
TagDescriptiontype is now exported from RTKQ.API endpoints now have a
.namefield containing the endpoint name, such as"getPokemon".Calling
promise.abort()on acreateAsyncThunkpromise before an asyncconditionresolves will now be treated as if theconditionitself returnedfalse, bailing out and not dispatching anything.The
mergeoption now receives a third argument containing{arg, baseQueryMeta, fulfilledTimeStamp, requestId}, in case that info is useful in deciding how to merge.The
@reduxjs/rtk-codemodspackage has been updated to fix cases where thecreateSliceBuildercodemod didn't preserve fields with function variable arguments, like[todoAdded]: adapter.addOne. That package has been updated to v0.0.3.What's Changed
Fix createAsyncThunk.withTypes
Update timer polyfills to work in more environments
Retry now checks whether potential retry counts are undefined, rather than boolean, in order to avoid filtering out 0's
Fix multiple small issues with 1.9
fulfillWithValue should infer return value
Fix Identifier/MemberExpression values in createSliceBuilder codemod
Additional 1.9.1 fixes
1.9.0
4 Nov 2022- deprecated
getRunningQueryThunk(endpointName, queryArgs) - deprecated
getRunningMutationThunk(endpointName, fixedCacheKeyOrRequestId) - deprecated
getRunningQueriesThunk()
This feature release adds several new options for RTK Query's
createApiandfetchBaseQueryAPIs, adds a newupsertQueryDatautil, rewrites RTKQ's internals for improved performance, adds a newautoBatchEnhancer, deprecates the "object" syntax forcreateReducerandcreateSlice.extraReducers, deprecates and removes broken utils for getting running query promises, improves TS inference, exports additional types, and fixes a number of reported issues.npm i @reduxjs/toolkit@latest yarn add @reduxjs/toolkit@latest
We plan to start work on RTK 2.0 in the next few weeks. RTK 2.0 will focus on dropping legacy build compatibility and deprecated APIs, with some potential new features. See the linked discussion thread and give us feedback on ideas!
Deprecations and Removals
Object Argument for
createReducerandcreateSlice.extraReducersRTK's
createReducerAPI was originally designed to accept a lookup table of action type strings to case reducers, like{ "ADD_TODO" : (state, action) => {} }. We later added the "builder callback" form to allow more flexibility in adding "matchers" and a default handler, and did the same forcreateSlice.extraReducers.We intend to remove the "object" form for both
createReducerandcreateSlice.extraReducersin RTK 2.0. The builder callback form is effectively the same number of lines of code, and works much better with TypeScript.Starting with this release, RTK will print a one-time runtime warning for both
createReducerandcreateSlice.extraReducersif you pass in an object argument.As an example, this:
const todoAdded = createAction('todos/todoAdded'); createReducer(initialState, { [todoAdded]: (state, action) => {} }) createSlice({ name, initialState, reducers: {/* case reducers here */}, extraReducers: { [todoAdded]: (state, action) => {} } })should be migrated to:
createReducer(initialState, builder => { builder.addCase(todoAdded, (state, action) => {}) }) createSlice({ name, initialState, reducers: {/* case reducers here */}, extraReducers: builder => { builder.addCase(todoAdded, (state, action) => {}) } })Codemods for Deprecated Object Reducer Syntax
To simplify upgrading codebases, we've published a set of codemods that will automatically transform the deprecated "object" syntax into the equivalent "builder" syntax.
The codemods package is available on NPM as
@reduxjs/rtk-codemods. It currently contains two codemods:createReducerBuilderandcreateSliceBuilder.To run the codemods against your codebase, run
npx @reduxjs/rtk-codemods <TRANSFORM NAME> path/of/files/ or/some**/*glob.js.Examples:
npx @reduxjs/rtk-codemods createReducerBuilder ./src npx @reduxjs/rtk-codemods createSliceBuilder ./packages/my-app/**/*.ts
We also recommend re-running Prettier on the codebase before committing the changes.
These codemods should work, but we would greatly appreciate testing and feedback on more real-world codebases!
Object reducer codemod before/after examples Before:
createReducer(initialState, { [todoAdded1a]: (state, action) => { // stuff }, [todoAdded1b]: (state, action) => action.payload, }); const slice1 = createSlice({ name: "a", initialState: {}, extraReducers: { [todoAdded1a]: (state, action) => { // stuff }, [todoAdded1b]: (state, action) => action.payload, } })After:
createReducer(initialState, (builder) => { builder.addCase(todoAdded1a, (state, action) => { // stuff }); builder.addCase(todoAdded1b, (state, action) => action.payload); }) const slice1 = createSlice({ name: "a", initialState: {}, extraReducers: (builder) => { builder.addCase(todoAdded1a, (state, action) => { // stuff }); builder.addCase(todoAdded1b, (state, action) => action.payload); } })getRunningOperationPromisesDeprecation and ReplacementIn
v1.7.0, we added anapi.util.getRunningOperationPromises()method for use with SSR scenarios, as well as a singulargetRunningOperationPromise()method intended for possible use with React Suspense.Unfortunately, in #2477 we realized that both those methods have a fatal flaw - they do not work with multiple stores in SSR.
As of this release, we are immediately marking
getRunningOperationPromises()as deprecated and discouraging its use before we remove it completely in RTK 2.0! It will now throw both runtime and compile errors in development to enforce moving away from using it. However, we are leaving its existing behavior in production builds to avoid actual breakage.The
getRunningOperationPromise()util was experimental, and as far as we can tell not actually being used by anyone, so we are removinggetRunningOperationPromisecompletely in this release.As replacements, RTKQ now includes four new thunks attached to
api.util:- deprecated
getRunningQueryThunk(endpointName, queryArgs) - deprecated
getRunningMutationThunk(endpointName, fixedCacheKeyOrRequestId) - deprecated
getRunningQueriesThunk() - deprecated
getRunningMutationsThunk()
Usages would typically change like this:
-await Promise.all(api.util.getRunningOperationPromises()) +await Promise.all(dispatch(api.util.getRunningQueriesThunk()))
Changelog
New RTK Query
createApiOptionscreateApiendpoints now have several additional options that can be passed in, some of which are intended to work together.mergeOptionRTKQ was built around the assumption that the server is the source of truth, and every refetch replaces the cached data on the client. There are use cases when it would be useful to merge an incoming response into the existing cached data instead, such as pagination or APIs that return varying results over time.
Query endpoints can now accept a
merge(cachedData, responseData)callback that lets you do Immer-powered "mutations" to update the existing cached data instead of replacing it entirely.Since RTKQ assumes that each response per key should replace the existing cache entry by default, the
mergeoption is expected to be used with theserializeQueryArgsandforceRefetchoptions, as described below.serializeQueryArgsOptionRTK Query always serializes the cache key value, and uses the string as the actual key for storing the cache entry. The default serialization is the name of the endpoint, plus either the primitive value or a stable-serialized object. An example might be
state.api.queries['getPokemon("pikachu")'].RTKQ already supported customization of this serialization behavior at the
createApilevel. Now, each endpoint can specify its ownserializeQueryArgsmethod.The per-endpoint
serializeQueryArgsmay return either a string, an object, a number, or a boolean. If it's a string, that value will be used as-is. Otherwise, the return value will be run through the default serialization logic. This simplifies the common case of stripping out a couple unwanted object fields from the cache key.This option serves two main purposes: leaving out values that are passed in to an endpoint but not really part of the "key" conceptually (like a socket or client instance), and altering cache key behavior to use a single entry for the endpoint (such as in an infinite loading / pagination scenario).
Also, the
defaultSerializeQueryArgsutil is now exported.getPost: build.query<Post, { id: string; client: MyApiClient }>({ queryFn: async ({ id, client }) => { const post = await client.fetchPost(id) return { data: post } }, serializeQueryArgs: ({ queryArgs, endpointDefinition, endpointName }) => { const { id } = queryArgs // This can return a string, an object, a number, or a boolean. // If it returns an object, number or boolean, that value // will be serialized automatically via `defaultSerializeQueryArgs` return { id } // omit `client` from the cache key // Alternately, you can use `defaultSerializeQueryArgs`: // return defaultSerializeQueryArgs({ // endpointName, // queryArgs: { id }, // endpointDefinition // }) // Or create and return a string yourself: // return `getPost(${id})` }, }),forceRefreshoptionSometimes you may want to force a refetch, even though RTKQ thinks that the serialized query args haven't changed and there's already a fulfilled cache entry.
This can be used to force RTKQ to actually refetch. One expected use case is an "infinite pagination" scenario where there is one cache entry for the endpoint, different page numbers are given as query args, and the incoming responses are merged into the existing cache entry:
listItems: build.query<string[], number>({ query: (pageNumber) => `/listItems?page=${pageNumber}`, // Only have one cache entry because the arg always maps to one string serializeQueryArgs: ({ endpointName }) => { return endpointName }, // Always merge incoming data to the cache entry merge: (currentCache, newItems) => { currentCache.push(...newItems) }, // Refetch when the page arg changes forceRefetch({ currentArg, previousArg }) { return currentArg !== previousArg }, }),transformErrorResponseOptionSimilar to
transformResponse, endpoints can now specify atransformErrorResponseoption as well.upsertQueryDataUtilRTKQ already has an
updateQueryDatautil to synchronously modify the contents of an existing cache entry, but there was no way to create a new cache entry and its metadata programmatically.This release adds a new
api.util.upsertQueryDataAPI that allows creating a cache entry + its data programmatically. As with the other util methods, this is a thunk that should be dispatched, and you should pass in the exact cache key arg and complete data value you want to insert:dispatch( api.util.upsertQueryData('post', '3', { id: '3', title: 'All about cheese.', contents: 'I love cheese!', }) )The dispatch acts like all other RTKQ requests, so the process is async, and the thunk returns a promise that resolves when the upsert is complete.
RTK Query Performance Improvements
We've significantly rewritten RTK Query's internal implementation to improve performance, especially in cases where many components with query hooks mount at the same time. The middleware has been "flattened" and runs fewer internal checks against each action, subscription updates are grouped together, and some unnecessary memoized selectors have been removed. One consequence is that forgetting to add the RTKQ middleware now throws an error instead of logging a warning.
Overall, RTK Query processing time should be noticeably faster than it was in 1.8.
RTK Query also can take advantage of the new "auto-batch enhancer" (described below) for some additional perf optimization, and we recommend adding that to your Redux store configuration.
fetchBaseQueryOptionsfetchBaseQueryhas several new options for processing requests:You can now specify a
timeoutoption for both individual endpoints andfetchBaseQuery. If provided, requests that take longer than this value will automatically abort.fetchBaseQuerynow supports passing theresponseHandlerandvalidateStatusoptions directly tofetchBaseQueryitself, in addition to accepting it as part of specific endpoints. If provided, these options will be applied as defaults to all requests for that API, which simplifies using them on many endpoints. Providing them for endpoints overrides the global option.You can now specify a
jsonContentTypestring that will be used to set thecontent-typeheader for a request with a jsonifiable body that does not have an explicitcontent-typeheader. Defaults to"application/json".You can now specify a
isJsonContentTypecallback that checks to see if the request body or response body should be stringified. The default looks for values like"application/json"and"application/vnd.api+json". You can also now specifyresponseHandler: 'content-type'to have RTKQ automatically check to see whether a response should be treated as text or JSON.The
prepareHeadersmethod can now return void and does not have toreturn headers.Other RTK Query Changes
The
refetch()methods now return a promise that can be awaited.Query endpoints can now accept a
retryConditioncallback as an alternative tomaxRetries. If you provideretryCondition, it will be called to determine if RTKQ should retry a failed request again.New Auto-Batching Store Enhancer
There are several different ways to "batch actions" with Redux stores, ranging from reducers to debounced subscriber notifications.
RTK now includes a new
autoBatchEnhancer()store enhancer that uses a variation on the "debounced notification" approach, inspired by React's technique of batching renders and determining if an update is low-priority or high-priority.The enhancer looks for any actions tagged with an
action.meta[SHOULD_AUTOBATCH] = trueflag, and delays notifying subscribers until a queued callback runs. This means that if multiple "auto-batched" actions are dispatched in a row, there will be only one subscriber notification. However, if any "normal-priority" action without that flag are dispatched before the queued callback runs, the enhancer will notify subscribers immediately instead and ignore the callback.This allows Redux users to selectively tag certain actions for effective batching behavior, making this purely opt-in on a per-action basis, while retaining normal notification behavior for all other actions.
The enhancer defaults to using
requestAnimationFrame, but can also be configured to usequeueMicrotaskto run at the end of an event loop tick,setTimeout, or a user-provided callback.RTK Query's internals have been updated to mark several key actions as batchable. While the enhancer is purely opt-in, benchmarks indicate that it can help speed up UI performance with RTK Query, especially when rendering many components with query hooks. We recommend adding this enhancer to your store setup if you're using RTK Query:
const store = configureStore({ reducer, enhancers: (existingEnhancers) => { // Add the autobatch enhancer to the store setup return existingEnhancers.concat(autoBatchEnhancer()) }, })Additionally, there's a
prepareAutoBatchedutil that can be used to help add theSHOULD_AUTOBATCHflag to actions, designed for use withcreateSlice:const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 } as CounterState, reducers: { incrementBatched: { // Batched, low-priority reducer(state) { state.value += 1 }, // Use the `prepareAutoBatched` utility to automatically // add the `action.meta[SHOULD_AUTOBATCH]` field the enhancer needs prepare: prepareAutoBatched<void>(), }, // Not batched, normal priority decrementUnbatched(state) { state.value -= 1 }, }, })TypeScript Improvements
RTK 1.9 now requires TS 4.2 or greater, and supports through TS 4.9.
The action type strings generated by
createActionare now full TS template literals when possible.There's now a
createAsyncThunk.withTypes()method that creates a "pre-typed" version ofcreateAsyncThunkwith types like{state, dispatch, extra}baked in. This can be used to simplify customizingcreateAsyncThunkwith the right types once during app setup.RTK Query now exports TS types for "pre-typed hook results", for cases when you want to wrap the query/mutation hooks in your own code. Additionally, RTKQ also exports
BaseQueryApiand exposes TS-only types for endpoint definitions.configureStorenow correctly infers changes to the store shape from any store enhancers, such as adding actual extra fields tostore.RTK now exports additional types from
redux-thunk.Bug Fixes
Manually initiated RTKQ promises should resolve correctly.
Previous API tags are removed before adding new ones, instead of accidentally merging them together.
invalidateTagsworks correctly when dealing with persisted query state.What's Changed
Add
isJsonContentTypepredicate tofetchBaseQueryAdd
jsonContentTypetofetchBaseQueryoptionsAdd ThunkMiddleware to the re-exported types from redux-thunk
Add timeout option to fetchBaseQuery
createSlice: Use template literal types for action type
Add types for manually typing hook results in userland code
Add 'content-type' ResponseHandler
Feature : endpoint-specific args serializers
RFC: Add "merge" functionality
- deprecated
Add runtime deprecation warning for reducer object notation
Add the ability to type
StoreEnhancersBatch RTKQ "subscribe on reject" actions, and improve cache collection timer handling
Add transformErrorReponse to rtkq endpoints
Fix manually initiate()d rtk-query promises
Implement codemods for createReducer and createSlice builder
RFC: Expose endpoint types
Add createAsyncThunk.withTypes
Return promise from query result & hook
refetchRetry condition by http error response
Consolidate RTKQ middleware to simplify stack size
Try fixing createAsyncThunk issues with TS 4.8
Fix assorted issues with the RTKQ middleware refactor
Fix
upsertQueryDatarace situationsFix upsert
Export the BaseQueryApi interface from rtk-query so it can be used as a type in TypeScript without importing from dist/.
Fix retryCondition
error.Export ToolkitStore interface from configureStore
Reset
dispatchQueuedvariable after flushingAdd
forceRefetchtoQueryExtraOptionsSpeed up subscription behavior by tracking state in middleware
prepareHeadersdoes not need to returnheadersany moreAdd
codemods-cliand rewrite "object reducer" codemodsRemove alpha compatibility fallbacks
Fix
skipTokenbehaviour inuseQueryStatefetchBaseQuery: allowheadersoptionReplace
getRunningOperationPromise(s)withgetRunning(Query|Queries|Mutation|Mutations)ThunkGeneral pre-1.9-beta cleanup
Convert "middleware not registered" warning into a full error
Rework endpoint serializeQueryArgs to allow object/number returns
Allow for global
responseHandlerandvalidateStatusconfigurationFix refetches when sQA returns same value and queryArgs are object
Add an auto-batching enhancer that delays low-pri notifications and use with RTKQ
Check middleware registration directly to avoid persistence issues
Fix "running thunks" types and remove unnecessary RTKQ selectors
Make autobatching notification queueing configurable
Hopefully final v1.9 tweaks
1.8.6
9 Oct 2022- removed
Remove previous api tags before adding new provided tags
Fix the
dispatchtype inference to correctly handle read-only middleware arraystoolkit: Export "ThunkMiddleware" from redux-thunk
This bugfix release fixes a couple of issues with RTKQ endpoint tags not invalidating correctly, and tweaks the
dispatchtype inference to handle more variations of arrays.What's Changed
Fix the
dispatchtype inference to correctly handle read-only middleware arraystoolkit: Export "ThunkMiddleware" from redux-thunk
- removed
Remove previous api tags before adding new provided tags
Fix invalidateTags
1.8.5
19 Aug 2022Prevent
keepUnusedDataForvalues from overflowingsetTimeoutcounterRemove typeof undefined checks where not necessary 🐃🪒
Update RDT options types, and export those + AnyListenerPredicate
This bugfix releas fixes an issue with large
keepUnusedDataForvalues overflowing JS timers, exports the types for the Redux DevTools Extension option, and and improves behavior of URL string generation.Changelog
keepUnusedDataForTimer FixkeepUnusedDataForaccepts a value in seconds. When there are no more active subscriptions for a piece of data, RTKQ will set a timer usingsetTimeout, andkeepUnusedDataFor * 1000as the timer value.We've been advising users that if they want to keep data in the cache forever that they should use a very large value for
keepUnusedDataFor, such as 10 years in seconds.However, it turns out that JS engines use a 32-bit signed int for timers, and 32-bits in milliseconds is only 24.8 days. If a timer is given a value larger than that, it triggers immediately.
We've updated the internal logic to clamp the
keepUnusedDataForvalue to be between 0 andTHIRTY_TWO_BIT_MAX_TIMER_SECONDS - 1.Note that in RTK 1.9 (coming soon), RTKQ will also accept
Infinityas a specialkeepUnusedDataForvalue to indicate cached data should never be expired.Other Changes
RTK inlines the TS types for the Redux DevTools Extension options to avoid an extra dependency, but the TS type for the options object wasn't exported publicly. We now export the
DevToolsEnhancerOptionstype.The logic for generating a final URL has been updated to avoid adding an extra trailing
/.What's Changed
Prevent
keepUnusedDataForvalues from overflowingsetTimeoutcounterRemove typeof undefined checks where not necessary 🐃🪒
Update RDT options types, and export those + AnyListenerPredicate
Ensures there is no unexpected slash in url before query params
1.8.4
11 Aug 2022Prevent duplicate ids when updating id of an element with the id of an existing element
ApiProvider: Correctly register listeners, allow to disable listeners
Update devtools options TS types based on RDT 3.0
This bugfix release adds exported TS types for RTKQ hooks for use in wrapping logic, adds
useDebugValueto the hooks to improve display in the React DevTools, updates the inlined types for the Redux DevTools options, and fixes an issue increateEntityAdapterthat could result in duplicate IDs being stored.Changelog
RTKQ Hook Result Types
RTK's types heavily rely on inference to minimize the amount of type info users have to provide. However, this can also make it difficult to write functions that wrap calls to RTK APIs.
Some users have asked to have types that help them write "higher-order hooks". RTK now exports types that represent "the return object for a query/mutation hook with a given value":
TypedUseQueryHookResultandTypedUseMutationResult. Both require<ResultType, QueryArg, BaseQuery>as generics, like this:const baseQuery = fetchBaseQuery({url: "https://some.server"}); type CustomHookResult = TypedUseQueryHookResult<MyResultObject, MyArgObject, typeof baseQuery> const useMyCustomHook = (arg: MyArgObject) : CustomHookResult => { return api.useGetSomeDataQuery(arg); }Redux DevTools Options Fixes
As of Redux DevTools 3.0, some of field names for custom DevTools options have changed to
actionsAllowlistandactionsDenylist. Since we inline the types instead of having a separate dependency, we've updated our TS types to match that. No runtime behavior was changed.Other Changes
RTKQ hooks now use
useDebugValueto give a better preview of the current value in the React DevTools "Component" tab.The
<ApiProvider>component now does a better job of registering and cleaning up focus listeners.Fixed a bug with
createEntityAdapterthat could allow duplicate IDs to be added depending on update parameters.What's Changed
Prevent duplicate ids when updating id of an element with the id of an existing element
ApiProvider: Correctly register listeners, allow to disable listeners
Update devtools options TS types based on RDT 3.0
Typo in unhandled error message
Use useDebugValue in useQuery and useMutation hooks
Add types for manually typing hook results in userland code
1.8.3
30 Jun 2022Add Missing Else to enhanceEndpoints Function
Check initial state is draftable before using immer to freeze it.
Check that body isJsonifiable before stringify
This bugfix release fixes a few minor issues and bits of behavior, including updating the React-Redux peer dep to
^8.0.2final, stable sorting increateEntityAdapter.updateManyand some initial state handling increateSlice.Changelog
React-Redux Peer Dep
We'd previously published an RTK build that accepted React-Redux v8 beta as a peer dep (for use with RTK Query). Since React-Redux v8 is out now, we've updated the peer dep to
^8.0.2.Entity Adapter Updates
Previously, applying updates via
createEntityAdapter.updateManycaused sorting order to change. Entities that had the same sorting result should have stayed in the same order relative to each other, but if one of those items had any updates, it would sort to the back of that group. This was due to items being removed from the lookup table and re-added, and since JS engines iterate keys in insertion order, the updated item would now end up compared later than before.We've reworked the implementation of
updateManyto avoid that. This also ended up fixing another issue where multiple update entries targeting the same item ID would only have the first applied.createSliceInitial StatecreateSlicenow logs an error ifinitialStateisundefined. This is most commonly seen when users misspellinitialState. It also has better handling for values that can't be frozen by Immer such as primitives.RTK Query
Several assorted improvements, including TS types for
BaseQueryand checking if the body can actually be safely stringified.What's Changed
Add Missing Else to enhanceEndpoints Function
Check initial state is draftable before using immer to freeze it.
Check that body isJsonifiable before stringify
Respect BaseQuery meta types when enhancing
Throw new error when initial state is undefined
Rewrite
updateManyto ensure stable sorting orderBump React-Redux peer dep
1.8.2
25 May 2022Add type @remarks for configureStore's middleware
Fix the "map values transpilation" bug, in yet another place
This bugfix release fixes a minor issue where calling
listenerMiddleware.startListening()multiple times with the sameeffectcallback reference would result in multiple entries being added. The correct behavior is that only the first entry is added, and later attempts to add the same effect callback reference just return the existing entry.What's Changed
Add type @remarks for configureStore's middleware
Fix the "map values transpilation" bug, in yet another place
1.8.1
31 Mar 2022This release updates RTK's peer dependencies to accept React 18 as a valid version. This should fix installation errors caused by NPM's "install all the peer deps and error if they don't match" behavior.
This release updates RTK's peer dependencies to accept React 18 as a valid version. This should fix installation errors caused by NPM's "install all the peer deps and error if they don't match" behavior.
React-Redux and React 18
Note: If you are now using React 18, we strongly recommend using the React-Redux v8 beta instead of v7.x!. v8 has been rewritten internally to work correctly with React 18's Concurrent Rendering capabilities. React-Redux v7 will run and generally work okay with existing code, but may have rendering issues if you start using Concurrent Rendering capabilities in your code.
Now that React 18 is out, we plan to finalize React-Redux v8 and release it live within the next couple weeks. We would really appreciate final feedback on using React-Redux v8 beta with React 18 before we publish the final version.
1.8.0
27 Feb 2022This release adds the new "listener" middleware, updates
configureStore's types to better handle type inference from middleware that overridedispatchreturn values, and updates our TS support matrix to drop support for TS < 4.1.Changelog
New "Listener" Side Effects Middleware
RTK has integrated the thunk middleware since the beginning. However, thunks are imperative functions, and do not let you run code in response to dispatched actions. That use case has typically been covered with libraries like
redux-saga(which handles side effects with "sagas" based on generator functions),redux-observable(which uses RxJS observables), or custom middleware.We've added a new "listener" middleware to RTK to cover that use case. The listener middleware is created using
createListenerMiddleware(), and lets you define "listener" entries that contain an "effect" callback with additional logic and a way to specify when that callback should run based on dispatched actions or state changes.Conceptually, you can think of this as being similar to React's
useEffecthook, except that it runs logic in response to Redux store updates instead of component props/state updates.The listener middleware is intended to be a lightweight alternative to more widely used Redux async middleware like sagas and observables. While similar to thunks in level of complexity and concept, it can replicate some common saga usage patterns. We believe that the listener middleware can be used to replace most of the remaining use cases for sagas, but with a fraction of the bundle size and a much simpler API.
Listener effect callbacks have access to
dispatchandgetState, similar to thunks. The listener also receives a set of async workflow functions liketake,condition,pause,fork, andunsubscribe, which allow writing more complex async logic.Listeners can be defined statically by calling
listenerMiddleware.startListening()during setup, or added and removed dynamically at runtime with specialdispatch(addListener())anddispatch(removeListener())actions.The API reference is available at:
redux-toolkit.js.org/api/createListenerMiddlewa…
Basic usage of the listener middleware looks like:
import { configureStore, createListenerMiddleware } from '@reduxjs/toolkit' import todosReducer, { todoAdded, todoToggled, todoDeleted, } from '../features/todos/todosSlice' // Create the middleware instance and methods const listenerMiddleware = createListenerMiddleware() // Add one or more listener entries that look for specific actions. // They may contain any sync or async logic, similar to thunks. listenerMiddleware.startListening({ actionCreator: todoAdded, effect: async (action, listenerApi) => { // Run whatever additional side-effect-y logic you want here console.log('Todo added: ', action.payload.text) // Can cancel other running instances listenerApi.cancelActiveListeners() // Run async logic const data = await fetchData() // Pause until action dispatched or state changed if (await listenerApi.condition(matchSomeAction)) { // Use the listener API methods to dispatch, get state, // unsubscribe the listener, start child tasks, and more listenerApi.dispatch(todoAdded('Buy pet food')) listenerApi.unsubscribe() } }, }) const store = configureStore({ reducer: { todos: todosReducer, }, // Add the listener middleware to the store. // NOTE: Since this can receive actions with functions inside, // it should go before the serializability check middleware middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(listenerMiddleware.middleware), })You can use it to write more complex async workflows, including pausing the effect callback until a condition check resolves, and forking "child tasks" to do additional work:
// Track how many times each message was processed by the loop const receivedMessages = { a: 0, b: 0, c: 0, } const eventPollingStarted = createAction('serverPolling/started') const eventPollingStopped = createAction('serverPolling/stopped') listenerMiddleware.startListening({ actionCreator: eventPollingStarted, effect: async (action, listenerApi) => { // Only allow one instance of this listener to run at a time listenerApi.unsubscribe() // Start a child job that will infinitely loop receiving messages const pollingTask = listenerApi.fork(async (forkApi) => { try { while (true) { // Cancellation-aware pause for a new server message const serverEvent = await forkApi.pause(pollForEvent()) // Process the message. In this case, just count the times we've seen this message. if (serverEvent.type in receivedMessages) { receivedMessages[ serverEvent.type as keyof typeof receivedMessages ]++ } } } catch (err) { if (err instanceof TaskAbortError) { // could do something here to track that the task was cancelled } } }) // Wait for the "stop polling" action await listenerApi.condition(eventPollingStopped.match) pollingTask.cancel() }, })configureStoreMiddleware Type ImprovementsMiddleware can override the default return value of
dispatch.configureStoretries to extract any declareddispatchtype overrides from themiddlewarearray, and uses that to alter the type ofstore.dispatch.We identified some cases where the type inference wasn't working well enough, and rewrote the type behavior to be more correct.
TypeScript Support Matrix Updates
RTK now requires TS 4.1 or greater to work correctly, and we've dropped 4.0 and earlier from our support matrix.
Other Changes
The internal logic for the serializability middleware has been reorganized to allow skipping checks against actions, while still checking values in the state.
What's Changed
Since most of the implementation work on the middleware was done over the last few months, this list only contains the most recent PRs since 1.7.2. For details on the original use case discussions and the evolution of the middleware API over time, see:
RTK PR #547: yet another attempt at an action listener middleware
RTK discussion #1648: New experimental "action listener middleware" package available
PRs since 1.7.2:
Rewrite MiddlewareArray and gDM for better Dispatch inference
Change listener middleware API name and signature
alm: Add cancellation message to TaskAbortError, listenerApi.signal & forkApi.signal.
: missing type export
Reorganize serializable check conditions
alm: Prevent zombie listeners caused by forked tasks
Integrate the listener middleware into the RTK package
alm: Cancel forkApi.delay and forkApi.pause if listener is cancelled or completed
Enable cancelling active listeners when unsubscribing
v1.8.0 integration
1.7.2
3 Feb 2022meta: Passes meta to result description functions [#1904]
Fix
addMatchertypingsPass baseQueryMeta into calculateProvidedBy
This release fixes a TS types bug with RTK Query generated selectors, makes the RTKQ structural sharing behavior configurable, adds an option to have the serializability middleware ignore all actions, and has several minor bugfixes and enhancements to RTK Query.
Changelog
RTK Query Selector TS Types Fix
Several users had reported that as of 1.7.0 selectors generated via
apiSlice.endpoint.select()were failing to compile when used, with TS errors that looked likeType '{}' is missing the following properties from type 'CombinedState<>.We've fixed the issue, and selectors should now compile correctly when used with TS.
Additional Configuration Options
RTK Query implements a technique called "structural sharing" to preserve existing object references if possible when data for an endpoint is re-fetched. RTKQ recurses over both data structures, and if the contents appear to be the same, keeps the existing values. That helps avoid potential unnecessary re-renders in the UI, because otherwise the entire re-fetched result would be new object references.
However, this update process can potentially take time depending on the size of the response. Endpoints can now be given a
structuralSharingoption that will turn that off to save on processing time:const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: "https://example.com" }), endpoints: (build) => ({ getEveryEntityInADatabase: build.query({ query: () => ({ url: "/i-cant-paginate-data" }), structuralSharing: false, }), }), });Additionally, the serializability check middleware can now be customized with an
ignoreActionsoption to exempt all actions from being checked. This is an escape hatch and isn't recommended for most apps:const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoreActions: true, }, }), });Other API Improvements
If an
extraArgumentwas provided to the thunk middleware during store configuration, that value is now passed along to theprepareHeaders()function:const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ thunk: { extraArgument: { myCustomApiService }, }, }), }); // ..later on const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: "https://example.com", prepareHeaders: async (headers, { getState, extra }) => { const token = getState().auth.token; const somethingElse = await extra.myCustomApiService.someMethod(); // do things with somethingElse return headers; }, }), });The
invalidatesTags/providesTagsfunctions now receive theaction.metafield as an argument, to help with potentially invalidating based on request/response headers.Bug Fixes
refetchOnFocusnow cleans up cache entries if a focus event is received and there are no active subscriptions, to avoid unnecessary requests.Active polls are cleaned up when the last component for a given subscription unsubscribes.
The types for
builder.addMatcherhave been updated to support inference of guards without atypeproperty.What's Changed
meta: Passes meta to result description functions [#1904]
Fix
addMatchertypingsPass baseQueryMeta into calculateProvidedBy
Cleanup polls on unsubscribeQueryResult
Add
extratoprepareHeaders, update documentation + testsFix
reducerPathfor query definitionsUpdate serialize documentation link
Refetch should not happen if no active subscribers
Add
ignoreActionsflag to serializable state middlewareRTKQ: Configurable
structuralSharingon endpoints/queries/createApi
1.7.0
10 Dec 2021Updates RTK Query with support for SSR and rehydration
Allows sharing mutation results across components
Adds a new
currentDatafield to query results
This feature release has a wide variety of API improvements:
Updates RTK Query with support for SSR and rehydration
Allows sharing mutation results across components
Adds a new
currentDatafield to query resultsAdds several new options for customizing endpoints and base queries
Adds support for async
conditionoptions increateAsyncThunkUpdates
createSlice/createReducerto accept a "lazy state initializer" functionUpdates
createSliceto avoid potential circular dependency issues by lazy-building its reducerUpdates Reselect and Redux-Thunk to the latest versions with much-improved TS support and new selector customization options
Fixes a number of small code and types issues
npm i @reduxjs/toolkit@latest yarn add @reduxjs/toolkit@latest
Changelog
RTK Query
RTK Query SSR and Rehydration Support
RTK Query now has support for SSR scenarios, such as the
getStaticProps/getServerSidePropsAPIs in Next.js. Queries can be executed on the server using the existingdispatch(someEndpoint.initiate())thunks, and then collected using the newawait Promise.all(api.getRunningOperationPromises())method.API definitions can then provide an
extractRehydrationInfomethod that looks for a specific action type containing the fetched data, and return the data to initialize the API cache section of the store state.The related
api.util.getRunningOperationPromise()API adds a building block that may enable future support for React Suspense as well, and we'd encourage users to experiment with this idea.Sharing Mutation Results Across Components
Mutation hooks provide status of in-progress requests, but as originally designed that information was unique per-component - there was no way for another component to see that request status data. But, we had several requests to enable this use case.
useMutationhooks now support afixedCacheKeyoption that will store the result status in a common location, so multiple components can read the request status if needed.This does mean that the data cannot easily be cleaned up automatically, so the mutation status object now includes a
reset()function that can be used to clear that data.Data Loading Updates
Query results now include a
currentDatafield, which contains the latest data cached from the server for the current query arg. Additionally,transformResponsenow receives the query arg as a parameter. These can be used to add additional derivation logic in cases when a hooks query arg has changed to represent a different value and the existing data no longer conceptually makes sense to keep displaying.Data Serialization and Base Query Improvements
RTK Query originally only did shallow checks for query arg fields to determine if values had changed. This caused issues with infinite loops depending on user input.
The query hooks now use a "serialized stable value" hook internally to do more consistent comparisons of query args and eliminate those problems.
Also,
fetchBaseQuerynow supports aparamsSerializeroption that allows customization of query string generation from the provided arguments, which enables better interaction with some backend APIs.The
BaseQueryApiandprepareheadersargs now include fields forendpointname,typeto indicate if it's a query or mutation, andforcedto indicate a re-fetch even if there was already a cache entry. These can be used to help determine headers likeCache-Control: no-cache.Other RTK Query Improvements
API objects now have a
selectInvalidatedByfunction that accepts a root state object and an array of query tag objects, and returns a list of details on endpoints that would be invalidated. This can be used to help implement optimistic updates of paginated lists.Fixed an issue serializing a query arg of
undefined. Related, an empty JSON body now is stored asnullinstead ofundefined.There are now dev warnings for potential mistakes in endpoint setup, like a query function that does not return a
datafield.Lazy query trigger promises can now be unwrapped similar to mutations.
Fixed a type error that led the endpoint return type to be erroneously used as a state key, which caused generated selectors to have an inferred
state: neverargument.Fixed
transformResponseto correctly receive theoriginalArgsas its third parameter.api.util.resetApiStatewill now clear out cached values inuseQueryhooks.The
RetryOptionsinterface is now exported, which resolves a TS build error when using the hooks with TS declarations.RTK Core
createSliceLazy Reducers and Circular DependenciesFor the last couple years we've specifically recommended using a "feature folder" structure with a single "slice" file of logic per feature, and
createSlicemakes that pattern really easy - no need to have separate folders and files for/actionsand/constantsany more.The one downside to the "slice file" pattern is in cases when slice A needs to import actions from slice B to respond to them, and slice B also needs to listen to slice A. This circular import then causes runtime errors, because one of the modules will not have finished initializing by the time the other executes the module body. That causes the exports to be undefined, and
createSlicethrows an error because you can't passundefinedtobuilder.addCase()inextraReducers. (Or, worse, there's no obvious error and things break later.)There are well-known patterns for breaking circular dependencies, typically requiring extracting shared logic into a separate file. For RTK, this usually means calling
createActionseparately, and importing those action creators into both slices.While this is a rarer problem, it's one that can happen in real usage, and it's also been a semi-frequently listed concern from users who didn't want to use RTK.
We've updated
createSliceto now lazily create its reducer function the first time you try to call it. That delay in instantiation should eliminate circular dependencies as a runtime error increateSlice.createAsyncThunkImprovementsThe
conditionoption may now beasync, which enables scenarios like checking if an existing operation is running and resolving the promise when the other instance is done.If an
idGeneratorfunction is provided, it will now be given thethunkArgvalue as a parameter, which enables generating custom IDs based on the request data.The
createAsyncThunktypes were updated to correctly handle type inference when usingrejectWithValue().Other RTK Improvements
createSliceandcreateReducernow accept a "lazy state initializer" function as theinitialStateargument. If provided, the initializer will be called to produce a new initial state value any time the reducer is givenundefinedas its state argument. This can be useful for cases like reading fromlocalStorage, as well as testing.The
isPlainObjectutil has been updated to match the implementation in other Redux libs.The UMD builds of RTK Query now attach as
window.RTKQinstead of overwritingwindow.RTK.Fixed an issue with sourcemap loading due to an incorrect filename replacement.
Dependency Updates
We've updated our deps to the latest versions:
We've also lowered RTK's peer dependency on React from
^16.14to^16.9, as we just need hooks to be available.Other Redux Development Work
The Redux team has also been working on several other updates to the Redux family of libraries.
React-Redux v8.0 Beta
We've rewritten React-Redux to add compatibility with the upcoming React 18 release and converted its codebase to TypeScript. It still supports React 16.8+/17 via a
/compatentry point. We'd appreciate further testing from the community so we can confirm it works as expected in real apps before final release. For details on the changes, see:RTK "Action Listener Middleware" Alpha
We have been working on a new "action listener middleware" that we hope to release in an upcoming version of RTK. It's designed to let users write code that runs in response to dispatched actions and state changes, including simple callbacks and moderately complex async workflows. The current design appears capable of handling many of the use cases that previously required use of the Redux-Saga or Redux-Observable middlewares, but with a smaller bundle size and simpler API.
The listener middleware is still in alpha, but we'd really appreciate more users testing it out and giving us additional feedback to help us finalize the API and make sure it covers the right use cases.
RTK Query CodeGen
The RTK Query OpenAPI codegen tool has been rewritten with new options and improved output.
What's Changed
Fix "isLoading briefly flips back to
true" #1519createAsyncThunk: Async condition
Add
argtotransformResponseAdd
currentDataproperty to hook results.Use
useSerializedStableValuefor value comparisonuseLazyQuery: Added docs for preferCache option
Correctly handle console logs in tests
Add
resetmethod to useMutation hookAllow for "shared component results" using the
useMutationhook🐛 Fix bug with
useMutationshared resultsPass the ThunkArg to the idGenerator function
Support a custom paramsSerializer on fetchBaseQuery
SSR & rehydration support, suspense foundations
Add
endpoint,typeandforcedtoBaseQueryApiandprepareHeadersSplit off signature without
AsyncThunkConfigfor better inferenceUpdate createReducer to accept a lazy state init function
Add
selectInvalidatedByAllow for circular references by building reducer lazily on first reducer call
Update deps for 1.7
fetchBaseQuery: Return nullon empty body for JSON. Add DevWarnings
Add unwrap to QueryActionCreatorResult and update LazyQueryTrigger
Only set originalArgs if they're not undefined
Treat null as a valid plain object prototype in isPlainObject() in order to sync the util across reduxjs/* repositories
Export RetryOptions interface from retry.ts
Api.util.resetApiState should reset useQuery hooks
Fix issue where the global RTK object got overwritten in the UMD files
Fix broken sourcemap output due to bad filename replacement
Override unwrap behavior for buildInitiateQuery, update tests
Fix incorrect RTKQ endpoint definition types for correct selector typings
Use originalArgs in transformResponse