v1.7.0
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 results - adds several new options for customizing endpoints and base queries
- adds support for async
conditionoptions increateAsyncThunk - updates
createSlice/createReducerto accept a "lazy state initializer" function - updates
createSliceto avoid potential circular dependency issues by lazy-building its reducer - updates 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@latestChangelog
RTK Query
RTK Query SSR and Rehydration Support
RTK Query now has support for SSR scenarios, such as the getStaticProps/getServerSideProps APIs in Next.js. Queries can be executed on the server using the existing dispatch(someEndpoint.initiate()) thunks, and then collected using the new await Promise.all(api.getRunningOperationPromises()) method.
API definitions can then provide an extractRehydrationInfo method 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.
useMutation hooks now support a fixedCacheKey option 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 currentData field, which contains the latest data cached from the server for the current query arg. Additionally, transformResponse now 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, fetchBaseQuery now supports a paramsSerializer option that allows customization of query string generation from the provided arguments, which enables better interaction with some backend APIs.
The BaseQueryApi and prepareheaders args now include fields for endpoint name, type to indicate if it's a query or mutation, and forced to indicate a re-fetch even if there was already a cache entry. These can be used to help determine headers like Cache-Control: no-cache.
Other RTK Query Improvements
API objects now have a selectInvalidatedBy function 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 as null instead of undefined.
There are now dev warnings for potential mistakes in endpoint setup, like a query function that does not return a data field.
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: never argument.
Fixed transformResponse to correctly receive the originalArgs as its third parameter.
api.util.resetApiState will now clear out cached values in useQuery hooks.
The RetryOptions interface is now exported, which resolves a TS build error when using the hooks with TS declarations.
RTK Core
createSlice Lazy Reducers and Circular Dependencies
For the last couple years we've specifically recommended using a "feature folder" structure with a single "slice" file of logic per feature, and createSlice makes that pattern really easy - no need to have separate folders and files for /actions and /constants any 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 createSlice throws an error because you can't pass undefined to builder.addCase() in extraReducers. (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 createAction separately, 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 createSlice to 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 in createSlice.
createAsyncThunk Improvements
The condition option may now be async, which enables scenarios like checking if an existing operation is running and resolving the promise when the other instance is done.
If an idGenerator function is provided, it will now be given the thunkArg value as a parameter, which enables generating custom IDs based on the request data.
The createAsyncThunk types were updated to correctly handle type inference when using rejectWithValue().
Other RTK Improvements
createSlice and createReducer now accept a "lazy state initializer" function as the initialState argument. If provided, the initializer will be called to produce a new initial state value any time the reducer is given undefined as its state argument. This can be useful for cases like reading from localStorage, as well as testing.
The isPlainObject util has been updated to match the implementation in other Redux libs.
The UMD builds of RTK Query now attach as window.RTKQ instead of overwriting window.RTK.
Fixed an issue with sourcemap loading due to an incorrect filename replacement.
Dependency Updates
We've updated our deps to the latest versions:
- Reselect 4.1.x: Reselect has brand-new customization capabilities for selectors, including configuring cache sizes > 1 and the ability to run equality checks on selector results. It also now has completely rewritten TS types that do a much better job of inferring arguments and catch previously broken patterns.
- Redux Thunk 2.4.0: The thunk middleware also has improved types, as well as an optional "global override" import to modify the type of
Dispatcheverywhere in the app
We've also lowered RTK's peer dependency on React from ^16.14 to ^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 /compat entry 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" #1519 by @phryneas in #1520 - feat(createAsyncThunk): async condition by @thorn0 in #1496
- add
argtotransformResponseby @phryneas in #1521 - add
currentDataproperty to hook results. by @phryneas in #1500 - use
useSerializedStableValuefor value comparison by @phryneas in #1533 - fix(useLazyQuery): added docs for preferCache option by @akashshyamdev in #1541
- correctly handle console logs in tests by @phryneas in #1567
- add
resetmethod to useMutation hook by @phryneas in #1476 - allow for "shared component results" using the
useMutationhook by @phryneas in #1477 - 🐛 Fix bug with
useMutationshared results by @Shrugsy in #1616 - pass the ThunkArg to the idGenerator function by @loursbourg in #1600
- Support a custom paramsSerializer on fetchBaseQuery by @msutkowski in #1594
- SSR & rehydration support, suspense foundations by @phryneas in #1277
- add
endpoint,typeandforcedtoBaseQueryApiandprepareHeadersby @phryneas in #1656 - split off signature without
AsyncThunkConfigfor better inference by @phryneas in #1644 - Update createReducer to accept a lazy state init function by @markerikson in #1662
- add
selectInvalidatedByby @phryneas in #1665 - Update Yarn from 2.4 to 3.1 by @markerikson in #1688
- allow for circular references by building reducer lazily on first reducer call by @phryneas in #1686
- Update deps for 1.7 by @markerikson in #1692
- fetchBaseQuery: return nullon empty body for JSON. Add DevWarnings by @phryneas in #1699
- Add unwrap to QueryActionCreatorResult and update LazyQueryTrigger by @msutkowski in #1701
- Only set originalArgs if they're not undefined by @phryneas in #1711
- Treat null as a valid plain object prototype in isPlainObject() in order to sync the util across reduxjs/* repositories by @Ilyklem in #1734
- export RetryOptions interface from retry.ts by @colemars in #1751
- fix: api.util.resetApiState should reset useQuery hooks by @phryneas in #1735
- fix issue where the global RTK object got overwritten in the UMD files by @Antignote in #1763
- Update dependencies and selector types by @markerikson in #1772
- Fix broken sourcemap output due to bad filename replacement by @markerikson in #1773
- Override unwrap behavior for buildInitiateQuery, update tests by @msutkowski in #1786
- Fix incorrect RTKQ endpoint definition types for correct selector typings by @markerikson in #1818
- Use originalArgs in transformResponse by @msutkowski in #1819
Full Changelog: v1.6.2...v1.7.0