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 |
---|---|---|---|---|
<entry-point-name>.cjs.development.js |
CJS | ES5 | 'development' | Node / dev |
<entry-point-name>.cjs.production.min.js |
CJS | ES5 | 'production' | Node / prod |
<entry-point-name>.esm.js |
ESM | ES5 | Embedded | Bundler, legacy syntax |
<entry-point-name>.modern.development.js |
ESM | ES2017 | 'development' | Browser module, dev |
<entry-point-name>.modern.js |
ESM | ES2017 | Embedded | Bundler, modern syntax |
<entry-point-name>.modern.production.min.js |
ESM | ES2017 | 'production' | Browser module, prod |
<entry-point-name>.umd.js |
UMD | ES5 | 'development' | Browser script, dev |
<entry-point-name>.umd.min.js |
UMD | ES5 | 'production' | Browser script, prod |
Async Thunk Improvements
We've made several updates to the createAsyncThunk
API to support additional flexibility and use cases.
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)
Additional Async Thunk Options
createAsyncThunk
return 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
.
The action creators attached to each thunk should now be callable without needing access to internal implementation details. This enables using them in your own code and tests if needed.
Dependency Updates
RTK now depends on Redux 4.1, which shrinks bundle size by extracting error messages from the production builds.
We've also updated our Immer dependency to 9.x, which has improved TS types.
See their release notes:
- https://github.com/reduxjs/redux/releases/tag/v4.1.0
- https://github.com/immerjs/immer/releases/tag/v9.0.0
Other Changes
The miniSerializeError
util used by createAsyncThunk
is now exported from the main package, as is the new copyWithStructuralSharing
util from the RTK Query entry point.
configureStore
now throws errors if the middleware
arg is undefined, or if the provided middleware array contains undefined values.
Thanks
This release has been the work of many contributors working together. We'd like to recognize their efforts here:
- @phryneas : Created the RTK Query API, did most of the implementation work, and wrangled reams of angle brackets to get all the TS types working correctly
- @msutkowski: added additional RTKQ behavior, dogfooded the early alphas, wrote the initial RTKQ preview docs, and created almost all the RTKQ example projects
- @Shrugsy: wrote extensive sections of the RTKQ usage guide and API reference docs
- @markerikson: played sounding board for the early API iterations (and generally pestered Lenz and Matt by asking "Can we make that simpler?" every 5 minutes), ported the RTKQ codebase from its alpha repo into the main RTK repo, updated the build tooling to get the correct artifact output, did miscellaneous docs updates and cleanup, and publicized RTKQ everywhere
- @hardfist: implemented the ESBuild build tooling conversion
We'd also like to thank:
- @tannerlinsley, @TkDodo, and the React Query contributors, for pushing the "data caching is not state management" idea forward, helping inspire RTK Query and providing some friendly competition to help us all improve the ecosystem
- @brandonroberts and @SaulMoro for creating integrations between RTK Query and NgRx
- Everyone else who supplied feedback during our alpha and beta cycles, for helping improve RTKQ's behavior in SSR use cases, finding bugs, and helping shape the API
Changes
There have been far too many changes for us to list here individually :) See the complete lists of PRs in the original RTKQ alpha repo and the PRs merged during the RTK 1.6 integration cycle:
- https://github.com/rtk-incubator/rtk-query/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged
- https://github.com/reduxjs/redux-toolkit/pulls?q=is%3Apr+is%3Amerged+base%3Afeature%2Fv1.6-integration+
The complete codebase changes can be seen here: