Releases: reduxjs/redux-toolkit
v1.9.0-beta.0
This feature-complete preview release includes several new options for RTK Query API endpoints as well as the ability to "upsert" data, performance improvements to the RTK Query middleware, improvements to fetchBaseQuery
behavior, several TS types tweaks and additional type exports, a runtime deprecation warning for the object argument form of createReducer
and createSlice.extraReducers
, and codemods to update those methods.
Please try this out and give us feedback (even if it's just "tried updating and everything's fine")! We believe that all changes intended for 1.9 are stable and should work, but as always we'd appreciate real-world checks to confirm that.
npm i @reduxjs/toolkit@next
yarn add @reduxjs/toolkit@next
Docs updates for 1.9 are in progress and can be viewed here:
https://deploy-preview-2401--redux-starter-kit-docs.netlify.app/
Changes Since Alpha
Codemods for Deprecated Object Reducer Syntax
Per the description in 1.9.0-alpha.0
, we plan to remove the "object" argument from createReducer
and createSlice.extraReducers
in the future RTK 2.0 major version. In 1.9.0-alpha.0
, we added a one-shot runtime warning to each of those APIs.
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: createReducerBuilder
and createSliceBuilder
.
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!
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);
}
})
getRunningOperationPromises
Deprecation and Replacement
In v1.7.0
, we added an api.util.getRunningOperationPromises()
method for use with SSR scenarios, as well as a singular getRunningOperationPromise()
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 removing getRunningOperationPromise
completely in this release.
As replacements, RTKQ now includes four new thunks attached to api.util
:
getRunningQueryThunk(endpointName, queryArgs)
getRunningMutationThunk(endpointName, fixedCacheKeyOrRequestId)
getRunningQueriesThunk()
getRunningMutationsThunk()
Usages would typically change like this:
-await Promise.all(api.util.getRunningOperationPromises())
+await Promise.all(dispatch(api.util.getRunningQueriesThunk()))
Summary of Alpha Changes
See the previous release notes for more details.
RTK Query Options
- API endpoints:
upsertQueryData
util ( #2266 )- RTKQ middleware optimization ( #2641, #2759 )
fetchBaseQuery
options:- hook triggers return promises ( #2212 )
- retry conditions by response ( #2239 )
TS Types
- action types are now template literals ( #2250 )
- Store enhancer types ( #2550 )
createAsyncThunk.withTypes
( #2604 )- Several additional exported types:
TS Support Matrix
Redux Toolkit now requires TypeScript 4.2 or higher, and works correctly with TS 4.8.
v1.9.0-alpha.2
This feature preview release fixes broken behavior in the new upsertQueryData
API, fixes behavior with the serializeQueryArgs+merge
combination by adding a new forceRefetch
option, rewrites the internal subscription tracking to speed up mount/update times, adds new TS type exports, and includes the bug fixes from 1.8.6.
Changelog
upsertQueryData
Fix
We released the new upsertQueryData
util in 1.9.0-alpha.1
, but the behavior didn't actually work as intended in some cases. We've tweaked the implementation and it now should work correctly. Please try it out and let us know!
New forceRefresh
option and Infinite Loading Usages
In earlier alphas, we added a new serializeQueryArgs
endpoint option to allow customization of cache keys. This serves two 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.
Along with that, we also added a merge
option that lets you modify the contents of an existing cache entry when new data is received, instead always replacing it.
Due to the existing internal logic, these two options are insufficient for the infinite loading / pagination case. For example, changing useGetItemsQuery(pageNum)
from 1
to 2
starts the checks for fetching new data, but it bails out internally because there's already a cache entry that is status: fulfilled
for that endpoint and cache key.
We've added an additional forceRefresh
call back that receives {currentArg, previousArg, state, endpointState}
as arguments. Use this in combination with the other options to force this endpoint to refetch when args change despite a single cache entry already existing, such as this example:
forceRefetch({currentArg, previousArg}) {
// Assume these are page numbers
return currentArg !== previousArg
},
serializeQueryArgs({endpointName}) {
// Will only have one cache entry for this endpoint, because of the consistent name
return endpointName
},
merge(currentCacheData, responseData) {
// Always add incoming data to the existing cache entry
currentCacheData.push(...responseData)
}
The forceRefresh
option may also be useful in other app-specific scenarios as well.
Internal Subscription Logic Rewrite
RTKQ tracks subscription entries, and thus far has done so by saving them in the Redux state and updating that by dispatching actions as each query hook loads. We've seen that this can be a performance bottleneck in edge cases where hundreds of query hooks are being mounted at once. (This shouldn't be a concern in most apps, but some users were stress-testing things :) )
We've reworked the internal RTKQ middleware logic to track most of the subscription data inside the middleware and sync it to the Redux store at the end of an event loop tick. This should speed up those large list edge cases by cutting 90% of the startup time.
What's Changed
- fix
upsertQueryData
race situations by @phryneas in #2646 - Fix upsert by @phryneas in #2669
- Export the BaseQueryApi interface from rtk-query so it can be used as a type in TypeScript without importing from dist/. by @nsthorat in #2740
- Fix retryCondition
error
. by @phryneas in #2743 - fix: export ToolkitStore interface from configureStore by @adamhari in #2750
- Fix the
dispatch
type inference to correctly handle read-only middleware arrays by @dokmic in #2629 - fix(toolkit): export "ThunkMiddleware" from redux-thunk by @VinceOPS in #2745
- Remove previous api tags before adding new provided tags by @Bezmehrabi in #2702
- reset
dispatchQueued
variable after flushing by @phryneas in #2757 - Fix invalidateTags by @manceau-jb in #2721
- Add
forceRefetch
toQueryExtraOptions
by @schadenn in #2663 - Speed up subscription behavior by tracking state in middleware by @markerikson in #2759
Full Changelog: v1.9.0-alpha.1...v1.9.0-alpha.2
v1.8.6
This bugfix release fixes a couple of issues with RTKQ endpoint tags not invalidating correctly, and tweaks the dispatch
type inference to handle more variations of arrays.
What's Changed
- Fix the
dispatch
type inference to correctly handle read-only middleware arrays by @dokmic in #2629 - fix(toolkit): export "ThunkMiddleware" from redux-thunk by @VinceOPS in #2745
- Remove previous api tags before adding new provided tags by @Bezmehrabi in #2702
- Fix invalidateTags by @manceau-jb in #2721
Full Changelog: v1.8.5...v1.8.6
v1.9.0-alpha.1
This feature preview release adds new options and improves behavior for RTK Query.
As of this alpha, RTK 1.9 is feature-complete and we do not expect additional meaningful changes. The remaining work is filling out docs and preparing the "object reducer" codemods for release alongside 1.9.
Still no specific ETA for release, but we hope for Soon (TM) :)
Changelog
upsertQueryData
API
RTKQ already has an updateQueryData
util 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.upsertQueryData
API 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!',
})
)
RTKQ Middleware Performance Optimizations
RTKQ automatically generates a Redux middleware for each "API slice" / createApi
call. That middleware itself has been made up of 7 individual middleware internally composed into the final middleware, with each individual middleware responsible for a different task like managing polling or cache lifetimes.
While this worked well for encapsulating responsibilities, it also meant that every dispatched Redux action had to go through 7 additional middleware. This added multiple function calls to each dispatch's stack trace, and even if there isn't a noticeable perf hit, it wasn't efficient. We've also had some reports that users with multiple API slices were occasionally seeing "Maximum call stack size exceeded" errors.
We've rewritten the internals of the middleware to be more efficient. Actions now only receive extra processing if they're actually related to the API slice itself, and that processing is done via a loop over handling logic at the end of the middleware unwind phase, rather than nesting function calls through multiple sub-middleware. That should keep the call stack shorter in the cases where API actions really are being processed.
Other RTKQ Updates
The refetch()
methods now return a promise that can be awaited.
Query endpoints can now accept a retryCondition
callback as an alternative to maxRetries
. If you provide retryCondition
, it will be called to determine if RTKQ should retry a failed request again.
What's Changed
- return promise from query result & hook
refetch
by @phryneas in #2212 - retry condition by http error response by @kahirokunn in #2239
- implemented upsertQueryData functionality per #1720 #2007 by @barnabasJ in #2266
- Consolidate RTKQ middleware to simplify stack size by @markerikson in #2641
- Add TS 4.8 to the test matrix by @markerikson in #2642
- Try fixing createAsyncThunk issues with TS 4.8 by @markerikson in #2643
- Fix assorted issues with the RTKQ middleware refactor by @markerikson in #2644
Full Changelog: v1.9.0-alpha.0...v1.9.0-alpha.1
v1.9.0-alpha.0
This feature preview release adds new options and improves behavior for RTK Query, adds a runtime deprecation for the "object" form of createReducer/createSlice.extraReducers
, adds the ability to define a "pre-typed" version of createAsyncThunk
, improves TS inference of store enhancers, and exports additional TS types.
We hope to add a couple additional RTKQ options as part of the final 1.9 release, including an upsertQueryData
util. See the RTK 1.9 milestone for remaining items. No hard ETA yet, but ideally we'd like to publish 1.9 within the next couple weeks if we can wrap things up.
Notable Changes
Some highlights for this alpha:
RTKQ merge
Option
RTKQ 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.
Object Reducer Deprecation Warning
RTK's createReducer
API 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 for createSlice.extraReducers
.
We intend to remove the "object" form for both createReducer
and createSlice.extraReducers
in 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 createReducer
and createSlice.extraReducers
if 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) => {})
}
})
We have initial codemods in the repo that will help rewrite the object form to the builder form, and we'll publish those with instructions alongside 1.9 when it goes final.
RTKQ Internal Improvements
When query hooks mount, they dispatch actions to subscribe to the relevant data. The first hook to do so will dispatch a "subscription/fulfilled" action, and all further hooks asking for the same cache key will dispatch "subscription/rejected" actions. Both cause the reducer logic to add another entry to the subscription tracking.
The dispatching of individual "subscription/rejected" actions was causing perf issues when many components mounted at once, due to the number of extra dispatches. RTKQ now batches those into a single combined action per event loop tick, which improves perf for some many-component use cases noticeably.
TS Types
configureStore
now correctly infers changes to the store shape from any store enhancers.
There's now a createAsyncThunk.withTypes()
method that can be used to create a "pre-typed" version of createAsyncThunk
with types like {state, dispatch, extra}
baked in.
What's Changed
- Add
isJsonContentType
predicate tofetchBaseQuery
by @msutkowski in #2331 - Add
jsonContentType
tofetchBaseQuery
options by @msutkowski in #2403 - Add ThunkMiddleware to the re-exported types from redux-thunk by @orta in #2451
- add timeout option to fetchBaseQuery by @phryneas in #2143
- createSlice: use template literal types for action type by @phryneas in #2250
- add types for manually typing hook results in userland code by @phryneas in #2276
- Add 'content-type' ResponseHandler by @taylorkline in #2363
- feature : endpoint-specific args serializers by @michal-kurz in #2493
- RFC: add "merge" functionality by @phryneas in #1059
- Add runtime deprecation warning for reducer object notation by @markerikson in #2591
- feat: Add the ability to type
StoreEnhancers
by @fostyfost in #2550 - Batch RTKQ "subscribe on reject" actions, and improve cache collection timer handling by @markerikson in #2599
- add transformErrorReponse to rtkq endpoints by @dreyks in #1841
- Fix manually initiate()d rtk-query promises by @wesen in #2187
- Implement codemods for createReducer and createSlice builder by @markerikson in #2602
- RFC: Expose endpoint types by @phryneas in #1646
- add createAsyncThunk.withTypes by @phryneas in #2604
Full Changelog: v1.8.5...v1.9.0-alpha.0
v1.8.5
This bugfix releas fixes an issue with large keepUnusedDataFor
values overflowing JS timers, exports the types for the Redux DevTools Extension option, and and improves behavior of URL string generation.
Changelog
keepUnusedDataFor
Timer Fix
keepUnusedDataFor
accepts a value in seconds. When there are no more active subscriptions for a piece of data, RTKQ will set a timer using setTimeout
, and keepUnusedDataFor * 1000
as 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 keepUnusedDataFor
value to be between 0 and THIRTY_TWO_BIT_MAX_TIMER_SECONDS - 1
.
Note that in RTK 1.9 (coming soon), RTKQ will also accept Infinity
as a special keepUnusedDataFor
value 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 DevToolsEnhancerOptions
type.
The logic for generating a final URL has been updated to avoid adding an extra trailing /
.
What's Changed
- Prevent
keepUnusedDataFor
values from overflowingsetTimeout
counter by @markerikson in #2595 - remove typeof undefined checks where not necessary 🐃🪒 by @phryneas in #1726
- Update RDT options types, and export those + AnyListenerPredicate by @markerikson in #2596
- Ensures there is no unexpected slash in url before query params by @ygrishajev in #2470
Full Changelog: v1.8.4...v1.8.5
v1.8.4
This bugfix release adds exported TS types for RTKQ hooks for use in wrapping logic, adds useDebugValue
to the hooks to improve display in the React DevTools, updates the inlined types for the Redux DevTools options, and fixes an issue in createEntityAdapter
that 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": TypedUseQueryHookResult
and TypedUseMutationResult
. 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 actionsAllowlist
and actionsDenylist
. 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 useDebugValue
to 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 createEntityAdapter
that could allow duplicate IDs to be added depending on update parameters.
What's Changed
- fix: prevent duplicate ids when updating id of an element with the id of an existing element by @samatar26 in #2020
- ApiProvider: correctly register listeners, allow to disable listeners by @phryneas in #2277
- Update devtools options TS types based on RDT 3.0 by @markerikson in #2480
- fix: typo in unhandled error message by @MichielTondeur in #2538
- Use useDebugValue in useQuery and useMutation hooks by @kaankeskin in #2500
- add types for manually typing hook results in userland code by @markerikson in #2580
Full Changelog: v1.8.3...v1.8.4
v1.8.3
This bugfix release fixes a few minor issues and bits of behavior, including updating the React-Redux peer dep to ^8.0.2
final, stable sorting in createEntityAdapter.updateMany
and some initial state handling in createSlice
.
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.updateMany
caused 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 updateMany
to avoid that. This also ended up fixing another issue where multiple update entries targeting the same item ID would only have the first applied.
createSlice
Initial State
createSlice
now logs an error if initialState
is undefined
. This is most commonly seen when users misspell initialState
. 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 BaseQuery
and checking if the body can actually be safely stringified.
What's Changed
- Add Missing Else to enhanceEndpoints Function by @kinson in #2386
- Check initial state is draftable before using immer to freeze it. by @EskiMojo14 in #2378
- Check that body isJsonifiable before stringify by @ShaunDychko in #2330
- Respect BaseQuery meta types when enhancing by @TamasSzigeti in #2225
- Throw new error when initial state is undefined by @dannielss in #2461
- Rewrite
updateMany
to ensure stable sorting order by @markerikson in #2464 - Bump React-Redux peer dep by @markerikson in 3033a33
New Contributors
Full Changelog: v1.8.2...1.8.3
v1.8.2
This bugfix release fixes a minor issue where calling listenerMiddleware.startListening()
multiple times with the same effect
callback 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 by @msutkowski in #2252
- Fix the "map values transpilation" bug, in yet another place by @markerikson in #2351
Full Changelog: v1.8.1...v1.8.2
v1.8.1
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.