Skip to content

v1.6.0 : RTK Query!

Compare
Choose a tag to compare
@markerikson markerikson released this 07 Jun 13:52
· 2528 commits to master since this release

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's node_modules folder. If you see this issue, you may need to uninstall and reinstall react-redux with the latest version, to help ensure no redux 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 and createAsyncThunk 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 and isLoading 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 new getPendingMeta({arg, requestId}) callback that can be passed as part of the createAsyncThunk options object. This is necessary because the pending action is dispatched before the payload creator is even called.
  • fulfilled: there is a new fulfillWithMeta utility in the payload creator's thunkApi object, which can be used instead of returning the payload directly: return fulfillWithMeta(actualPayload, meta)
  • rejected: the existing rejectWithValue utility now also accepts a meta 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:

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:

The complete codebase changes can be seen here:

v1.5.1...v1.6.0