Dylan Tientcheu I build experiences to make your everyday life simpler.

RTK Query: The future of data fetching and caching for Redux

5 min read 1598

rtk-query-data-fetching

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.

Why would you use RTK Query ?

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.

Fetching and caching data with RTK Query

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.

We made a custom demo for .
No really. Click here to check it out.

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.

Mutating data

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:

  • Invalidating everything of a type
  • Selectively invalidating a list

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.

Where does caching comes into play?

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.

Query statuses

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.

Conditional fetching

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.

Error handling

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.

Polling

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

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.

Code generator

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.

Customizability

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 formats
  • endpoints, which is the set of operations you perform against your servers
  • setupListeners, which is a utility to help manage refetching either in a global or granular way
  • Much more to handle your API calls and Redux store

Comparison with react-query

RTK 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.

Conclusion

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.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Dylan Tientcheu I build experiences to make your everyday life simpler.

Leave a Reply