RTK Query is an experimental library from the Redux team with the main purpose of fetching and caching data for your web app. It utilizes Redux under the hood and is built on top of Redux Toolkit (RTK). RTK Query provides advanced setup options to handle your fetching and caching needs in the most flexible and efficient way possible.
While RTK Query utilizes Redux under the hood, that doesn’t mean you need a sophisticated understanding of Redux to work with it. But learning both Redux and RTK will go a long way toward harnessing the state management capabilities RTK Query can provide for your web app.
Today, RTK Query is still in its alpha stage, meaning it is subject to multiple breaking changes in its architecture and API. Nevertheless, it already delivers a simple and efficient solution for data caching and fetching.
RTK Query was built to address the need to fetch data when using Redux as your state management system. RTK Query can be added as a middleware and provides powerful React Hooks to help fetch your data. Despite its nascence, it’s safe to say that when RTK Query goes into production, it will be a perfect candidate to fetch data in JavaScript apps of all sizes.
In your typical small React app (without Redux), you’ll have the ability to fetch data with the integrated ApiProvider
. On the other hand, in a bigger app (using Redux), you can still plug in RTK Query as a middleware in your store.
RTK Query’s agnosticism makes it easy to integrate with any framework capable of using Redux (Vue.js, Svelte, Angular, etc.). Note that while RTK Query is coined agnostic, it is also extremely opinionated, following Redux’s established paradigms. In addition, RTK Query is built with TypeScript, thus providing first-class types support.
First things first: you need to set up your project to use RTK Query. We need to create a service definition that will fetch to our public API. For this example, we are using a Dungeons & Dragons API:
import { createApi, fetchBaseQuery } from "@rtk-incubator/rtk-query"; // Create your service using a base URL and expected endpoints export const dndApi = createApi({ reducerPath: "dndApi", baseQuery: fetchBaseQuery({ baseUrl: "https://www.dnd5eapi.co/api/" }), endpoints: (builder) => ({ getMonstersByName: builder.query({ query: (name: string) => `monsters/${name}` }) }) }); export const { useGetMonstersByNameQuery } = dndApi;
As mentioned in the docs, RTK Query prefers centralizing its data fetching configuration, which is unlikely in different data fetching libraries — part of what makes it opinionated. Centralizing our setup has its pros and cons. For example, keeping our fetch hooks together is not a bad idea, but this could quickly grow cumbersome if working with an extensive API.
The next step is to add this service to our store by adding our generated reducer and our API middleware. This will turn on caching, prefetching, polling, and other RTK Query features.
export const store = configureStore({ reducer: { [dndApi.reducerPath]: dndApi.reducer }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(dndApi.middleware) });
Next, you need to wrap the provider, as you’d do with a basic Redux store, then you can query in your components using your previously defined query hook.
import * as React from "react"; import { useGetMonstersByNameQuery } from "./services/dnd"; export default function App() { // Using a query hook automatically fetches data and returns query values const { data, error, isLoading } = useGetMonstersByNameQuery("aboleth"); return ( <div className="App"> {error ? (²²²²²²²²² <>Oh no, there was an error</> ) : isLoading ? ( <>Loading...</> ) : data ? ( <> <h3>{data.name}</h3> <h4> {data.speed.walk} </h4> </> ) : null} </div> ); }
The complete application code is available in CodeSandbox.
Sometimes you need to create or update your data. RTK Query helps you do that in a standard way. The provided useMutation
hook returns a tuple containing a trigger function and an object containing results from the trigger. In contrast to useQuery
, the mutation is only performed when the trigger is called.
In a much more advanced setting, you may have a use case in which you need to synchronize your local cache with the server after you perform a mutation. This is called revalidation. RTK Query provides two scenarios to deal with this use case, and you can decide which to use based on your project needs:
While using RTK Query mutations, you’ll also have all the tools you need to implement optimistic update behavior: you can use the onStart
phase of a mutation, where you manually set the cached data via updateQueryResult
. In case an error occurs, you can use onError
along with patchQueryResult
to roll back to the previous state.
Caching is automatic in RTK Query. If your data ever changes (i.e., is invalidated), refetching occurs automatically only for the elements that changed. This is handled via RTK Query’s powerful queryCachedKey
.
Once a request is made, RTK Query will serialize the parameters to provide a unique queryCachedKey
. This key is then verified in all future requests to prevent unnecessary refetching. If ever you need to go over this behavior, you can always call the refetch
function that is provided by your hook.
Caching behavior is neatly illustrated in the docs along with a CodeSandbox example. This shows how the automated fetching and caching features help reduce the amount of requests.
It is also important to note how big a pain it can be to lift the states returned by the query. RTK Query exposes the request states, which can be directly used in our app, as shown in the example below:
isUninitialized: false; // Query has not started yet. isLoading: false; // Query is currently loading for the first time. No data yet. isFetching: false; // Query is currently fetching, but might have data from an earlier request. isSuccess: false; // Query has data from a successful load. isError: false; // Query is currently in "error" state.
As mentioned above, useQuery
automatically fetches your data and handles the caching. RTK Query provides a way to stop a query from automatically running with a Boolean skip
parameter that can be added to the query hook, which will help manage this behavior. Setting skip
to false
strongly affects the way your data is fetched and cached.
Errors are returned through the error
property provided by the hook. RTK Query expects you to return payloads (errors or successes) in a particular format to help with type inference.
return { error: { status: 500, data: { message: 'error reasons' } };
If you need to edit your current return format, you can use a custom baseQuery
, which will help you mold your returned data.
To help you gracefully handle your errors, RTK Query exposes a retry
utility with which you can wrap your baseQuery
to create an exponential backoff of a specified number of attempts (maxRetries
).
In addition, RTK Query also enables you to manage your errors at a macro level, which may help you log errors for unfulfilled async calls.
You can also get the feel of having a real-time server by using the exposed pollingInterval
on your useQuery
hooks. This parameter takes in a number in milliseconds, which will later be the interval at which your data refreshes. Moreover, you can also manually refresh your data.
Prefetching is simply fetching data before it is actually needed — for instance, if you need the next page of paginated data fetched before it is actually requested.
RTK Query handles this by enabling you to send in two arguments: isOlderThan
and force
. isOlderThan
will run the query based on a Boolean or a timestamp, and force
will ignore the isOlderThan
argument and run the query even if it is present in the cache.
Since it’s centralized, and RTK Query works with your hooks, it may quickly become cumbersome to write full API endpoints in your service file. To deal with that, RTK Query provides a CodeGen that works with OpenAPI schemas.
It is crucial for every API client library to be fully customizable; a good example is Axios. This enables developers to have the capability to handle default behaviors, interceptors, and authentication without the need to repeat code.
createApi
is the main point where RTK Query will be configured. It exposes parameters such as:
baseQuery
, which could be customized to create interceptors or mold return formatsendpoints
, which is the set of operations you perform against your serverssetupListeners
, which is a utility to help manage refetching either in a global or granular wayRTK Query resembles react-query in the way it utilizes hooks. However, the two libraries have slightly different approaches.
RTK Query focuses on harnessing Redux’s power to provide a much more efficient and declarative method to fetch data. It also aims to be agnostic by nature, with a tight dependency on Redux Toolkit.
Their caching strategies are quite different as well: RTK Query is declarative in data invalidation, and its cache key is created via endpoints and arguments, whereas react-query uses a manual cached key for invalidation and caches by user defined query keys.
RTK Query provides a more extensive comparison here.
When it reaches production, RTK Query will surely be a great addition to teams using Redux for their state management. Early signs show great promise — it already delivers a simple and efficient solution.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowDesign React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.
Fix sticky positioning issues in CSS, from missing offsets to overflow conflicts in flex, grid, and container height constraints.
From basic syntax and advanced techniques to practical applications and error handling, here’s how to use node-cron.