Releases: reduxjs/redux-toolkit
v1.6.0 : RTK Query!
This release adds the new RTK Query data fetching APIs to Redux Toolkit. It also adds multiple new options to createAsyncThunk
for including meta
fields and working with results, updates dependencies to Redux 4.1 and Immer 9, and includes a complete rewrite of our build toolchain with additional "modern" build artifacts in the package.
While this is a minor release in terms of semver, this is a huge update in terms of functionality, scope, and effort. We're excited about how these new APIs will help our users build better applications with less code and better behavior!
Installation:
npm i @reduxjs/toolkit@latest
yarn add @reduxjs/toolkit@latest
Upgrade Note: During the alphas, we received some reports of users seeing incorrect types after installing the RTK 1.6 previews. The problems appeared to be caused by multiple versions of the
redux
package ending up in a project'snode_modules
folder. If you see this issue, you may need to uninstall and reinstallreact-redux
with the latest version, to help ensure noredux
duplicates are in the package tree.
Changelog
RTK Query Data Caching API
RTK Query is a powerful data fetching and caching tool. It is designed to simplify common cases for loading data in a web application, eliminating the need to hand-write data fetching & caching logic yourself.
RTK Query is an optional addon included in the Redux Toolkit package, and its functionality is built on top of the other APIs in Redux Toolkit.
See the RTK Query usage guides and API reference docs for complete information on how to use RTK Query:
https://redux-toolkit.js.org/rtk-query/overview
Motivation
Web applications normally need to fetch data from a server in order to display it. They also usually need to make updates to that data, send those updates to the server, and keep the cached data on the client in sync with the data on the server. This is made more complicated by the need to implement other behaviors used in today's applications:
- Tracking loading state in order to show UI spinners
- Avoiding duplicate requests for the same data
- Optimistic updates to make the UI feel faster
- Managing cache lifetimes as the user interacts with the UI
The Redux core has always been very minimal - it's up to developers to write all the actual logic. That means that Redux has never included anything built in to help solve these use cases. The Redux docs have taught some common patterns for dispatching actions around the request lifecycle to track loading state and request results, and Redux Toolkit's createAsyncThunk
API was designed to abstract that typical pattern. However, users still have to write significant amounts of reducer logic to manage the loading state and the cached data.
Over the last couple years, the React community has come to realize that "data fetching and caching" is really a different set of concerns than "state management". While you can use a state management library like Redux to cache data, the use cases are different enough that it's worth using tools that are purpose-built for the data fetching use case.
RTK Query takes inspiration from other tools that have pioneered solutions for data fetching, like Apollo Client, React Query, Urql, and SWR, but adds a unique approach to its API design:
- The data fetching and caching logic is built on top of Redux Toolkit's
createSlice
andcreateAsyncThunk
APIs - Because Redux Toolkit is UI-agnostic, RTK Query's functionality can be used with any UI layer
- API endpoints are defined ahead of time, including how to generate query parameters from arguments and transform responses for caching
- RTK Query can also generate React hooks that encapsulate the entire data fetching process, provide
data
andisLoading
fields to components, and manage the lifetime of cached data as components mount and unmount - RTK Query provides "cache entry lifecycle" options that enable use cases like streaming cache updates via websocket messages after fetching the initial data
- We have early working examples of code generation of API slices from OpenAPI and GraphQL schemas
- Finally, RTK Query is completely written in TypeScript, and is designed to provide an excellent TS usage experience
Basic Usage
RTK Query is included within the installation of the core Redux Toolkit package. It is available via either of the two entry points below:
import { createApi } from '@reduxjs/toolkit/query'
/* React-specific entry point that automatically generates
hooks corresponding to the defined endpoints */
import { createApi } from '@reduxjs/toolkit/query/react'
For typical usage with React, start by importing createApi
and defining an "API slice" that lists the server's base URL and which endpoints we want to interact with:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { Pokemon } from './types'
// Define a service using a base URL and expected endpoints
export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getPokemonByName: builder.query<Pokemon, string>({
query: (name) => `pokemon/${name}`,
}),
}),
})
// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useGetPokemonByNameQuery } = pokemonApi
The "API slice" also contains an auto-generated Redux slice reducer and a custom middleware that manages suscription lifetimes. Both of those need to be added to the Redux store:
import { configureStore } from '@reduxjs/toolkit'
// Or from '@reduxjs/toolkit/query/react'
import { setupListeners } from '@reduxjs/toolkit/query'
import { pokemonApi } from './services/pokemon'
export const store = configureStore({
reducer: {
// Add the generated reducer as a specific top-level slice
[pokemonApi.reducerPath]: pokemonApi.reducer,
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(pokemonApi.middleware),
})
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
setupListeners(store.dispatch)
Finally, import the auto-generated React hooks from the API slice into your component file, and call the hooks in your component with any needed parameters. RTK Query will automatically fetch data on mount, re-fetch when parameters change, provide {data, isFetching}
values in the result, and re-render the component as those values change:
import * as React from 'react'
import { useGetPokemonByNameQuery } from './services/pokemon'
export default function App() {
// Using a query hook automatically fetches data and returns query values
const { data, error, isLoading } = useGetPokemonByNameQuery('bulbasaur')
// render UI based on data and loading state
}
Bundle Size
RTK Query adds a fixed one-time amount to your app's bundle size. Since RTK Query builds on top of Redux Toolkit and React-Redux, the added size varies depending on whether you are already using those in your app. The estimated min+gzip bundle sizes are:
- If you are using RTK already: ~9kb for RTK Query and ~2kb for the hooks.
- If you are not using RTK already:
- Without React: 17 kB for RTK+dependencies+RTK Query
- With React: 19kB + React-Redux, which is a peer dependency
Adding additional endpoint definitions should only increase size based on the actual code inside the endpoints
definitions, which will typically be just a few bytes.
The functionality included in RTK Query quickly pays for the added bundle size, and the elimination of hand-written data fetching logic should be a net improvement in size for most meaningful applications.
Build Tooling Improvements
We've completely replaced our previous TSDX-based build tooling pipeline with a custom build pipeline based on ESBuild and TypeScript. This should have no visible changes behavior-wise for end users - we've kept the same build artifact names and ES syntax levels. However, it does speed up our own build process, which is important now that we're generating many more output files.
The published package now also includes a set of "modern" ESM build artifacts that target ES2017 syntax instead of ES5. These files should be smaller than the current "ESM" artifact, which is uses the ES module format but with ES5-level syntax for backwards compatibility.
Most bundlers should currently pick up the ESM artifact as the default, such as in Create-React-App projects. If you are planning to drop IE11 compatibility, you should be able to modify your bundler config to import the modern artifact instead. Since the modern artifact includes the usual process.env.NODE_ENV
checks for build tools to use, we also have pre-compiled versions for "modern dev" and "modern prod" that are suitable for use in browsers or ESM-centric build tools.
We've also done an optimization pass on both the RTK core and the RTK Query sections to improve tree shaking.
See this table for details on the generated artifacts, which are available for each of the entry points:
Redux Toolkit Build Artifacts
| Filename | Module | Syntax | process.env
| Purpose |
|----------------------------------------|--------|-------...
v1.6.0-rc.1
This release candidate finalizes the upcoming RTK Query APIs. It adds new options to createAsyncThunk
for adding meta
fields to thunk-dispatched actions, updates the createApi
lifecycle handlers to pass through those meta
fields, and removes fields that were deprecated during the alpha process. We've also fleshed out the RTKQ preview docs with significant new content.
This should be the final preview before the RTK 1.6 final release, barring any unforeseen last-minute bugs. (Note: This is the same content as rc.0, but we found a bug in our build process that had wrong output in that release. Hopefully no more issues!)
The preview docs are located at https://deploy-preview-1016--redux-starter-kit-docs.netlify.app .
Installation:
npm i @reduxjs/toolkit@next
yarn add @reduxjs/toolkit@next
Changelog
Async Thunk meta
Support
createAsyncThunk
automatically generates action creators and action types, and then automatically dispatches those actions during execution. This simplifies the process of creating and using thunks, but like all abstractions, also limits flexibility.
One limitation has been that there was no way to customize the meta
field to the actions generated by createAsyncThunk
, and some users needed to add additional metadata to actions for use by other middleware.
We've updated createAsyncThunk
to allow adding additional contents to meta
. The approach varies based on the action type:
pending
: there is a newgetPendingMeta({arg, requestId})
callback that can be passed as part of thecreateAsyncThunk
options object. This is necessary because thepending
action is dispatched before the payload creator is even called.fulfilled
: there is a newfulfillWithMeta
utility in the payload creator'sthunkApi
object, which can be used instead of returning the payload directly:return fulfillWithMeta(actualPayload, meta)
rejected
: the existingrejectWithValue
utility now also accepts ameta
argument:return rejectWithValue(failedPayload, meta)
API Lifecycle meta
Support
The createApi
cache lifecycle callbacks like onCacheEntryAdded
now make the meta
field available as part of the promise result, such as cacheDataLoaded
.
Code Cleanup and Types Tweaks
The fields such as onStart
and onSuccess
that were deprecated in earlier alphas have been removed.
The now-unused ApiWithInjectedEndpoints
type was removed.
Various APIs that accept arrays were updated to mark those array parameters as readonly
, to help indicate that they shouldn't be mutated, and to ensure that arrays marked as const
can be accepted.
The ApiModules
type was modified to prevent a inferred type of this node exceeds the maximum length the compiler will serialize
TS error.
Docs Updates
We've updated to Docusaurus 2.0-beta.0, which includes Webpack 5. No visible change for you, but faster CI builds for us thanks to build artifact caching! (Our docs builds dropped from around 7 minutes each build down to around 25 seconds, thanks to only changed files being rebuilt.)
We've also completed filling out the RTK Query API docs.
Changes
Code
- Remove upcoming alpha.3 deprecations (#1052 - @phryneas)
- createAsyncThunk: support for
meta
(#1083 - @phryneas) - Add baseQueryMeta to thunks, meta to lifecycle results (#1083 - @phryneas)
- Remove ApiWithInjectedEndpoints references (#1112 - @msutkowski)
- Add
readonly
to array-type function params (#1113 - @phryneas) - remove
Id
to force lazy evaluation of API type (#1116 - @phryneas)
Docs
- Wayyyy too many docs PRs to even try to list here :) (@Shrugsy)
- Wayyyy too many examples moved from sandboxes to even try to list here :) (@msutkowski)
- Match redux config for docusaurus webpack v5 (#1086 - @msutkowski)
- Some assorted content tweaks (@markerikson)
v1.6.0-rc.0
This release is broken due to a build misconfiguration - please see rc.1
instead:
https://github.com/reduxjs/redux-toolkit/releases/tag/v1.6.0-rc.1
v1.6.0-beta.0
This beta release improves the in-progress RTK Query APIs. It adds a new onCacheEntryAdded
lifecycle callback to enable streaming cache updates, adds per-endpoint cache timeout overrides and additional options for skipping queries, and fixes issues with query arg serialization and build output, We've also fleshed out the RTKQ preview docs with significant new content.
We are hopeful that this will be the final pre-release with any meaningful API changes, and that the remaining work should just be final polish of the documentation before this goes live.
The preview docs are located at https://deploy-preview-1016--redux-starter-kit-docs.netlify.app .
Installation:
npm i @reduxjs/toolkit@next
yarn add @reduxjs/toolkit@next
Changelog
Cache Entry Lifecycle
RTK Query is built around standard HTTP endpoint request/response-style API calls. However, today's applications often use a hybrid approach, where initial data is fetched via a request, but then further updates are streamed via a websocket or other persistent connection.
Endpoint definitions now support an onCacheEntryAdded
lifeycle callback. This callback will be executed whenever a new endpoint subscription entry is added to the cache (ie, when a component requests a specific endpoint+params combination that is not currently being loaded).
The onCacheEntryAdded
callback allows you to run additional logic after the initial fetch completes and after the entry is removed from the cache, and provides tools to update the existing cache for this query as needed. The intended use case is to open a websocket-type connection, and update the cached data over time as messages are received. A typical usage might look like:
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: "/" }),
endpoints: (build) => ({
getMessages: build.query({
query: (channel) => `messages/${channel}`,
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved }
) {
// wait for the initial query to resolve before proceeding
await cacheDataLoaded;
// Update our query result when messages are received
const unsubscribe = ChatAPI.subscribeToChannel(
arg.channelId,
(message) => {
// Dispatches an update action with the diff
updateCachedData((draft) => {
draft.push(message);
});
}
);
// Clean up when cache subscription is removed
await cacheEntryRemoved;
unsubscribe();
},
}),
}),
});
This adds significant additional flexibility in interacting with cached data.
Additional API Polish
createApi
supports a keepUnusedDataFor
option to modify the default cache expiration time, but that can now be overridden on a per-endpoint basis as well.
The selectFromResult
option has been reworked so that it receives the standard hook result structure as its input, and you can extract and return whatever pieces of that data are actually needed by this component.
RTKQ now exports a skipToken
value that can be passed to queries as an indicator that the query should be skipped for now, in addition to the existing skip
flag option. This is primarily useful for working around some TS inference issues with hook return types.
The copyWithStructuralSharing
util is now exported.
The updateQueryResult
and pathQueryResult
util methods have been renamed to updateQueryData
and patchQueryData
.
Optimistic updates can now be reverted by calling .undo()
, which automatically dispatches the appropriate inverse patch update action.
Fixes
The MiddlewareArray
type has been tweaked to produce correct behavior when transpiled.
The default query arg serialization logic now handles nested values correctly.
Docs Updates
We've significantly improved the preview RTK Query documentation. We've added pages on "Streaming Updates", "Cached Data", "Customizing Queries", and "Migrating to RTK Query". We've also fleshed out the API references for the generated React hooks, added more details to descriptions of queries and endpoints, and filled out info on how cache lifetime behavior works. Thanks to @Shrugsy for writing most of the docs updates!
Changes
Code
- export Result Definition types (#1022 - @phryneas)
- add export for
copyWithStructuralSharing
(#1026 - @phryneas) - fix/default serialize query args (#1029 - @phryneas)
- split up middleware, add OptionalPromise, add cache entry lifecycle (#1034 - - @phryneas)
- simplify
selectFromResult
usage (#1035 - @phryneas) - fix MiddlewareArray for new build (#1038 - @phryneas)
- remove last traces of TSDX (#1039 - @phryneas)
- prevent badly typed
Middleware<any>
from castingdispatch
toany
(#1042 - @phryneas) - remove alpha.2 deprecation fallbacks (#1051 - @phryneas)
- add a per-endpoint keepUnusedDataFor option (#1053 - @phryneas)
BaseQueryApi.signal
is not optional (#1058 - @phryneas)- allow using skipSymbol as arg in hooks (#1056 - @phryneas)
- nest data structure for
cacheDataLoaded
andqueryFulfilled
(#1078 - @phryneas)
Docs
- Docs - add 'Cached data' concept page (#1036 - @Shrugsy)
- Docs - RTKQ - Convert codeblocks to TS & enable codeblock transpilation (#1042 - @Shrugsy)
- Docs - add Streaming Updates page (#1049 - @Shrugsy)
- Docs - add "Customizing Queries" page (#1057 - @Shrugsy)
- Docs - Add "Migrating to RTK Query" page (#1060 - @Shrugsy)
- Docs - add RTK Query content to "Getting Started" page (#1066 - @Shrugsy)
- Docs - expand explanation of cache lifetime and subscription behavior (#1071 - @Shrugsy)
- Docs - extend detail for Queries and Endpoints (#1074 - @Shrugsy)
v1.6.0-alpha.2
This release fixes a bug with RTK Query cache invalidation, and exposes the useLazyQuerySubscription
hook.
Installation:
npm i @reduxjs/toolkit@next
yarn add @reduxjs/toolkit@next
Changelog
Invalidation Bugfix
We saw that RTK Query cache invalidation was actually broken in alpha.1
. After investigation, it looks like TypeScript was outputting incorrect code for looping over a set.values()
iterator. We've tweaked the logic to work around that.
Please upgrade to this release ASAP, as invalidation is a key part of RTK Query's functionality.
useLazyQuerySubscription
The useLazyQuerySubscription
hook was documented, but wasn't actually being included in the output. We've fixed that.
Additional Build Tweaks
We're still fiddling with the combination of ESBuild and TypeScript for our recently updated build chain, and have flipped a couple more switches in the process. Should be no user-visible difference.
Changes
- Expose
useLazyQuerySubscription
for endpoints ( #1017 - @Shrugsy) - Fix bad transpilation of set iteration breaking invalidation (#1020 - @markerikson)
v1.6.0-alpha.1
This pre-1.6 alpha release integrates the RTK Query source code into the Redux Toolkit repo, publishes the RTK Query APIs as nested endpoints in the @reduxjs/toolkit
package, and publishes a docs preview containing the RTK Query API and usage guide docs integrated into the RTK docs site. We've also bumped our Redux dependency to 4.1.0.
This release also contains the changes from 1.6.0-alpha.0, including Immer 9.x and the improvements to createAsyncThunk
and createEntityAdapter
.
Note: we have published additional bugfixes since alpha.1
. Please see the releases list for details and be sure to update to @reduxjs/toolkit@next
.
Installation:
npm i @reduxjs/toolkit@next
yarn add @reduxjs/toolkit@next
Changelog
RTK Query Integration
RTK Query is a data fetching and caching library built for Redux. It's most similar to React Query, Apollo, Urql, and SWR. The idea is that you just say "here's my URL endpoint and some query params", and it handles the entire process of:
- Starting to fetch the data when needed
- Managing loading state
- Caching the data
- Re-rendering your component when the loading state changes or the data is available
- Clearing the cache when the last component that needs the data is unmounted
- Enabling automatic polling of the data if desired
So, instead of having to write a bunch of thunks, action creators, reducers, useEffects
, and dispatching yourself, you just do:
// src/services/pokemon.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
// Define a service using a base URL and expected endpoints
export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getPokemonByName: builder.query({
query: (name: string) => `pokemon/${name}`,
}),
}),
})
// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useGetPokemonByNameQuery } = pokemonApi
// src/App.tsx
import { useGetPokemonByNameQuery } from './services/pokemon'
export default function App() {
// Using a query hook automatically fetches data and returns query values
const { data, error, isLoading } = useGetPokemonByNameQuery('bulbasaur')
// render logic here
}
We originally developed RTK Query as a standalone package at https://github.com/rtk-incubator/rtk-query in order to enable faster iteration during the alpha process.
Now that the RTK Query APIs have stabilized, we've merged all of the RTK Query source code and docs content back into the main RTK repo. From there, we've updated our build tooling and package publishing to include the RTK Query code inside the @reduxjs/toolkit
package, but as separate nested entry points.
// Existing RTK APIs
import { createSlice } from '@reduxjs/toolkit'
// RTK Query APIs, with UI-agnostic logic
import { createApi } from '@reduxjs/toolkit/query'
// Same RTK Query APIs, but with React-specific functionality built in
import { createApi } from '@reduxjs/toolkit/query/react'
If you've been using RTK Query in its standalone alpha package, you should be able to migrate by installing this RTK alpha, and just switching your imports to match the examples above.
Since these are separate entry points from the root package, you won't pay any bundle size cost unless you specifically import the RTK Query APIs.
We have not yet done extensive testing of the merged package - that's why this is an alpha! That said, we've successfully been able to locally publish a preview version of the package and use it in a standalone version of the RTKQ "kitchen sink with React" demo app, which is a standard CRA project. We've also verified that the app builds correctly with both TS 4.1 (including named hooks exports using string literal syntax) and TS 4.0 (with just the per-endpoint hook subfields).
For visualization purposes, here's what the bundle size analysis for that app looks like:
In general, we believe that this alpha should run correctly in varying environments, but we specifically request that users try this out and let us know if you run into any problems.
We also expect some additional final tweaks to the RTKQ APIs before 1.6 is released, but do not expect any large breaking changes.
RTK Query Docs
We've also merged the RTK Query docs content and added it to a preview version of the RTK docs site. We'll leave this preview up during the RTK 1.6 pre-release process. Here's the links to the primary new docs pages:
- Tutorial: https://deploy-preview-1016--redux-starter-kit-docs.netlify.app/tutorials/rtk-query
- Usage guide: https://deploy-preview-1016--redux-starter-kit-docs.netlify.app/usage/rtk-query/queries
- API reference: https://deploy-preview-1016--redux-starter-kit-docs.netlify.app/api/rtk-query/createApi
Note that we still need to fill out additional docs content for the RTK APIs and update some of the examples! We'll be working to finish that before the final release.
Redux 4.1.0
Since we just released Redux 4.1.0, this release also bumps our Redux dependency to match that. This will shave about 1K off your existing min+gz bundle sizes.
Changes
- chore(build): improved build tools (#981 - @hardfist)
- Migrate RTK Query contents into the RTK repo (#1006 - @markerikson)
- Restructure RTK files (#1007 - @markerikson)
- Try to build bundles for RTK Query (core and React) (#1011 - @markerikson)
- Integrate RTKQ tests (#1012 - @markerikson)
- Attempt to get TS version selection working (#1013 - @markerikson)
- Add full types to useMutation's returned callback (#1014 - @Shrugsy)
- Move RTKQ API docs over (#1015 - @markerikson)
https://github.com/reduxjs/redux-toolkit/compare/v1.6.0-alpha.0..v1.6.0-alpha.1
v1.6.0-alpha.0
This initial pre-1.6 alpha release reworks our build tooling to enable upcoming changes, updates dependencies, adds new entity adapter CRUD methods, and tweaks async thunk options.
We have several major improvements planned for later pre-1.6 alphas, including merging the "RTK Query" APIs back into RTK itself and making those available as subpackages.
Changelog
Build Tooling Updates
We've replaced our existing TSDX build setup with a new custom setup based on ESBuild + TypeScript (thanks to @hardfist for doing all the work on that!). In theory, we should have all of the same build artifacts we had before, targeting the same browser and ES spec levels. We did add a new set of "modern" build artifacts that target ES2017, with builds for bundlers and browwsers. However, we can't officially list those in package.json
yet, as apparently defining an exports
key can break Node and is considered a breaking change. We'll look at flipping that on in a future 2.0 release.
Please let us know if you see any bugs that may be related to the build tooling and bundling changes!
Dependency Updates
We've updated Immer to 9.0.x. Also, we've updated Redux to the hot-off-the-presses 4.1.0-alpha.0, which shrinks bundle size by extracted error messages in production. Again, please report any problems you run into.
New Entity Adapter Replace Methods
createEntityAdapter
already had methods to add, upsert, and update items. We've added setOne
and setMany
, which let you add or entirely replace existing items.
Async Thunk Improvements
createAsyncThunk
promises now have a .unwrap()
method that returns a promise for the actual result payload, or throws an error if the promise was rejected. This simplifies the use case of working with the thunk result in components.
createAsyncThunk
also now accepts an idGenerator
option to let you swap out the default nanoid()
ID generation utility for your own, such as uuid4
.
Other Updates
We've done a bit of internal cleanup in a few functions to simplify and shorten implementations.
configureStore
now checks for accidentally returning undefined
for a middleware array, or undefined
entries in the array.
The PreloadedState
type should now work better with TS 4.3+.
Changes
- Bump Redux to 4.1.0-alpha.0 0169356
- Update immer to v9.0.1 (#977) 087d4b6
- Add idGenerator option to createAsyncThunk (#743) (#976) 309c4bd
- Add simpler unwrapping of createAsyncThunk promises (#970) 8301370
- Merge branch 'next' 247f9fe
- Remove unnecessary condition (#846) 37d22c4
- Add check for undefined middleware in configureStore (#959) 86f7eea
- chore(build): move tsdx to esbuild (#957) cb0535d
- Add setOne and setMany to entity adapter (#969) 795af40
- Removed unnecessary sort in sorted_state_adapter.ts#merge() (#960) 4462647
- Extract common utils from both adapter files (#952) 61ec24c
- export AsyncThunkOptions (#950) 7db3e48
- fix PreloadedState type for TS 4.3 (#949) 9abaec4
v1.5.1
This release updates createReducer/createSlice
to ensure initial state gets frozen, optimizes the performance of the immutability and serializability dev check middleware, and re-exports the original
and isDraft
APIs from Immer.
Changes
Freezing Initial State
Immer has always frozen its result states in development, and as of Immer 8, does so in production as well. Since both createReducer
and createSlice
use Immer, all their result states get frozen.
However, the initial state was never being run through Immer, and thus was never being frozen. That meant it was actually possible to mutate initial state values.
We now run the initial state through an Immer no-op reducer, just to ensure that it's properly frozen at all times.
Dev Check Middleware Optimizations
The immutability and serializability dev check middleware act as safety rails to catch common mistakes. However, both of them do so by running deep recursive checks on all of your Redux state, after every dispatched action. Those deep checks are often very expensive, especially as your state grows larger, and the performance can add noticeable lag in dev. RTK offers options for turning them off or ignoring specific slices of state, and warns if they're taking too long, but those are escape hatches we would rather people not use because they lose the safety benefits.
In this release, we've optimized both middleware to run significantly faster.
The immutability middleware now considers frozen objects to be immutable by default, and will stop recursing when it sees a frozen value. This means that in most cases, the runtime of the immutability middleware is effectively 0ms, as it only has to do a few quick checks on your root state slice values without going any deeper. We've also optimized the nested keypath calculation logic, which cuts down on runtime significantly if it does actually have to recurse.
We've also applied the keypath optimizations to the serializability middleware as well. While this middleware can't benefit from the frozen checks, the keypath optimizations have cut its runtime down by about 70% in benchmarks.
Additional Immer Re-Exports
We now export the original
and isDraft
APIs from Immer for completeness.
This release also updates the Immer dependency to 8.0.1+ to address a vulnerability in Immer. Our dependency was already 8.0.0+, so strictly speaking no update was necessary - users just needed to update their own local dependencies to get the newer Immer version.
Docs Updates
We've made several updates to the RTK docs:
- The existing tutorials in the RTK docs site have been removed, and we now point to the "Redux Essentials" and the "Redux Fundamentals" tutorials in the Redux core docs so that there's only one place to have to read through tutorials
- We've added a couple "quick start" tutorial pages that show the bare basics of setting up RTK and using it with TypeScript
Changelog
- Export isDraft from Immer (#944 - @markerikson)
- Optimize dev check middleware performance (#943 - @markerikson)
- Freeze initial state by passing through Immer (#940 - @markerikson)
- fix: vulnerability in immer (#909 - @i1skn
- re-export original from immer (#832- @jackielii)
v1.5.0
This release updates Immer to v8.x, adds a set of "matcher" utilities to simplify checking if dispatched actions match against a range of known action types, adds additional customization of async thunk error handling, and adds miscellaneous improvements around TS types and some reducer edge cases.
Changes
Immer v8
In RTK v1.4, we upgraded Immer to v7.x, which added a new current
API for debugging.
Immer recently released v8.0.0, which changes its default behavior around auto-freezing state values. Previously, Immer only auto-froze data in development mode, partly under the assumption that freezing would be slower overall. Due to internal changes in Immer 7, Immer now checks if data is frozen and can bail out of some of its processing early. As a result, Immer 8 switches to always freezing return values, even in production, Per discussion in the Immer issues linked from the v8 release announcement, this apparently is actually faster on average.
This is a noticeable change in behavior, but we consider it not breaking for RTK on the grounds that you shouldn't be mutating state outside of a reducer anyway, so there shouldn't be any visible effect on correctly-written Redux logic.
We've updated our Immer dependency to v8.x.
Per the Immer docs on auto-freezing, it may be more efficient to shallowly pre-freeze very large data that won't change by using Immer's freeze
utility. RTK now re-exports freeze
as well.
Action Matching Utilities
In RTK v1.4, we added the ability to write "matching reducers" that can respond to more than one potential action based on a predicate function, such as builder.addMatcher( (action) => action.type.endsWith('/pending'), reducer)
.
Many users have asked for the ability to directly list a series of RTK-generated action creators as possible actions to match against. For RTK 1.5, we're adding several utilities that will help handle that process.
First, we've added isAnyOf(matchers)
and isAllOf(matchers)
. These effectively perform boolean ||
and &&
checks, and accept an array containing RTK action creators or action-matching predicates. They return a new matching callback that can be passed to builder.addMatcher()
.
const isActionSpecial = (action: any): action is SpecialAction => {
return action.payload === 'SPECIAL'
}
const thunkA = createAsyncThunk<string>('a', () => 'result');
// later
createSlice({
name,
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addMatcher(isAllOf(isActionSpecial, thunkA.fulfilled), reducer);
}
})
When used with TypeScript, isAllOf
and isAnyOf
will correctly narrow down the possible type of action
based on the actions they match against.
We've also added a set of matching utilities specifically meant to help check if a given action corresponds to the lifecycle actions dispatched by some specific async thunks. isPending
, isFulfilled
, isRejected
, isRejectedWithValue
, and isAsyncThunkAction
all accept an array of thunks generated by createAsyncThunk
, and will match the corresponding action types from those thunks:
const thunkA = createAsyncThunk<string>('a', () => 'result')
const thunkB = createAsyncThunk<string>('b', () => 'result')
// later
createSlice({
name,
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addMatcher(isFulfilled(thunkA, thunkC), reducer);
}
})
They can also be used as standard TS type guards:
if (isFulfilled(action)) {
// TS will narrow the type of `action` to match the standard "fulfilled" fields
}
createAsyncThunk
Improvements
We've fixed a bug in the unwrapResult
utility where it didn't correctly handle use of rejectWithValue()
in the thunk. It will now correctly re-throw the value passed into rejectWithValue()
.
The auto-generated requestId
is now attached to the promise returned when the thunk is dispatched, as is the arg
that was passed into the thunk when it was dispatched.
We've added a new serializeError
callback option to createAsyncThunk
. By default, createAsyncThunk
will convert thrown errors into a serializable equivalent format, but you can override the serialization and provide your own callback if desired.
Draft-Safe Selectors
Memoized selectors created with Reselect's createSelector
don't work well with Immer's Proxy-wrapped draft states, because the selectors typically think the Proxy object is the same reference and don't correctly recalculate results as needed.
We've added a createDraftSafeSelector
API that lightly wraps createSelector
by checking if the initial argument (usually state
) is actually an Immer draft value, and if so, calling current(state)
to produce a new JS object. This forces the selector to recalculate the results.
We've also updated createEntityAdapter
's getSelectors
API to use these draft-safe selectors.
In general, using selectors inside of reducers is an unnecessary abstraction - it's fine to access data like state.entities[id].value = 123
. However, a number of users have expressed an interest in doing so, so we've made these changes to help accommodate that.
Other Changes
We now export our internal isPlainObject
implementation.
If an Immer-powered reducer has null
as its value, returning undefined
is now accepted.
TS types for case reducers have been tweaked to allow returning Draft<T>
to handle an edge case with generics.
The TS types for the devTools.serialize
option in configureStore
have been updated to correctly match the actual values.
The RTK docs now use a custom Remark plugin created by @phryneas, which allows us to write real TS code for code blocks, compile it to verify it type-checks correctly, and generate a plain JS version of that exact same example, with the TS and JS variations viewable in tabs for each code block.
You can see that in action in pages like the createSlice
API docs:
https://redux-toolkit.js.org/api/createSlice
Changelog
- allow to return undefined for null-states in caseReducers (#631 - @phryneas)
- fix
serialize
types (#752 - @blaiz) - Allow CaseReducers to also return
Draft<T>
(#756 - @phryneas) - Add
requestId
property to the promise returned bycreateAsyncThunk
(#784 - @phryneas) - Export isPlainObject (#795 - @msutkowski)
- fix unwrapResult behavior (#704 - @phryneas)
- isAnyOf/isAllOf HOFs for simple matchers (#788 - @douglas-treadwell)
- AsyncThunk matchers (#807 - @douglas-treadwell)
- Update Immer to 8.x (#821 - @markerikson)
- [entityAdapter] handle draft states in selectors (#815 - @phryneas)
- cAT: serializeError option (#812 - @phryneas)
v1.4.0
This release updates Immer to v7.x, adds new options for defining "matcher" and "default case" reducers, and adds a new option for adding middleware to the store.
Changes
Immer v7
Immer recently released v7.0.0. The main feature is a new current
API, which takes a Proxy
-wrapped draft value and returns a finalized snapshot of the draft at that point in time.
Logging draft states in RTK reducers has always been difficult, as browsers display proxies in a hard-to-read format. This utility allows a return to logging partially-updated data to see the results, like console.log(current(state))
.
We've updated our Immer dependency to v7.x, and now export current
as part of our public API.
"Matcher" and "Default Case" Reducers
createReducer
has always been designed to handle exact action types. Both the object form and "builder callback" form let you specify a specific action type string to match against.
This is by far the most common use case, but we didn't previously have a way to handle matching a range of possible action types based on some criteria. We also had some requests to add some kind of a "default case" handler, similar to the default
keyword in a switch
statement.
The builder callback form of createReducer
now supports two new methods in addition to the existing builder.addCase
method:
builder.addMatcher
accepts a predicate function that looks like(action: Action) => boolean
, and a case reducer. If the matcher returnstrue
, the case reducer will run. Multiple matchers may be added tocreateReducer
, and all matchers that returntrue
will run in the order they were defined after any exact case match has run.builder.addDefaultCase
will run a reducer if no other cases or matchers have run.
Example:
const increment = createAction('increment')
const decrement = createAction('decrement')
createReducer(0, builder =>
builder
.addCase(increment, (state, action) => {
// action is inferred correctly here
})
// You can chain calls, or have separate `builder.addCase()` lines each time
.addCase(decrement, (state, action) => {})
// You can match a range of action types
.addMatcher(
action => action.endsWith('rejected'),
(state, action) => {}
)
// and provide a default case if no other handlers matched
.addDefaultCase((state, action) => {})
)
The new builder methods work the same way in the extraReducers
field of createSlice
as well.
Middleware Creation
We already export getDefaultMiddleware
to allow you to customize the middleware setup when creating the store. However, running [...getDefaultMiddleware, otherMiddleware]
often loses type information when used with TypeScript, and middleware types may need to reference the root state type as well.
The middleware
option for configureStore
can now be a callback function that receives a strongly-typed version of getDefaultMiddleware
as an argument, and should return the final middleware array. It also includes strongly-typed concat
and prepend
methods that preserve type information better than spreading arrays:
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(logger)
})
Bug Fixes
createAsyncThunk
could sometimes skip handling a promise under certain conditions, causing an "Uncaught Promise" warning.- The
updateMany
CRUD method ofcreateEntityAdapter
wasn't properly merging together multiple changes for the same item ID
Typing Updates
The condition
argument for createAsyncThunk
is now defined as returning boolean | undefined
. The actual code explicitly checks if condition() !== false
, so returning undefined
is legal, but the typing was too strict.
We now export the return type for createAsyncThunk
for reuse.
Docs Updates
We've extensively updated the createReducer
and createSlice
pages to cover the new builder methods, and configureStore
and getDefaultMiddleware
to cover the new middleware syntax.
We've extracted the info on the immutability and serializability dev check middleware into their own separate API reference pages.
We've added a Usage Guide section on handling "non-serializable value" warnings, including examples of configuration for use with Redux-Persist and React-Redux-Firebase. Related to that, the serializability warning message now includes a link to this section.
The API reference section now has subcategories for "Store Setup", "Reducers and Actions", and "Other".
We've enabled Dark Mode for Docusaurus, including tweaks to colors for better contrast.
Changes
- Update docs on serializability usage and dev check middleware (@markerikson - #630)
- add
addMatcher
to builder notations &actionMatchers
argumen… (@phryneas - #610) - Add styling for blockquotes on darkmode (@msutkowski - #615)
- Add section to usage guide on working with non-serializable data (@cloeper - #623)
- Fixed multiple updates with same id in updateMany (@jakeboone02 - #621)
- Bump immer to v7, export current util, add usage docs (@mutkowksi - #604)
- docs: implement dark mode (@sreetamdas - #575)
- Export return type of createAsyncThunk (@smrq - #574)
- Prevent unhandled promises in createAsyncThunk (@msutkowski - #570)
- Adding correctly typed
prepend` and
concat` to the array… (@phryneas - #559) - add middlewareBuilder notation with type-curried arguments to co… (@phryneas - #549)
- Allow undefined return type for createAsyncThunk options (@melanieseltzer - #595)