paint-brush
Coding Productively With RTK Query: Simplifying API Data Fetching and Cachingby@theexceel
200 reads

Coding Productively With RTK Query: Simplifying API Data Fetching and Caching

by Excel OlatunbosunSeptember 14th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Rather than trying to write reducers ourselves, we’re now going to focus on defining “where is this data coming from?”, “how should this update be sent?”, “When should this cached data be re-fetched?”, and “How should the cached data be updated?”
featured image - Coding Productively With RTK Query: Simplifying API Data Fetching and Caching
Excel Olatunbosun HackerNoon profile picture

Important note: I tried to put as many code examples as possible to explain better. If you have difficulty understanding the code, I did a step-by-step walk-through at the end. Enjoy!


RTK Query is a tool that simplifies the process of data fetching and the overall management of cached data for redux applications. It completely eliminates the need to hand-write data fetching and caching logic yourself. RTK Query is an optional add-on in the Redux Toolkit package.


Meaning it is built on top of the other APIs in Redux Toolkit.


Typically, a web application will need to fetch data from a server to display. It also needs to make updates to that data, send those updates to the server, and keep cached data on the client in sync with the data on the server.


Before RTK Query, implementing behaviors like loading, caching, and error components was convoluted. Even with the use of createAsyncThunk with createSlice, there is a fair amount of work to achieve certain implementations.


RTK Query was introduced in June 2021, by the team in charge of Redux Toolkit.


RTK Query primarily consists of two APIs:


  • createApi(): The core of RTK Query's functionality. It allows you to define a set of endpoints that describe how to retrieve data from a series of endpoints, including the configuration of how to fetch and transform that data. In most cases, you should use this once per app, with "one API slice per base URL" as a rule of thumb.


  • fetchBaseQuery(): A small wrapper around fetch that aims to simplify requests. It is used to perform the actual network requests and handle the communication with your API server.


Together, createApi and fetchBaseQuery help you set up the foundation for making API requests and handling responses in RTK Query.

Features of RTK Query

  • Automatic API endpoint definition: RTK Query allows you to define API endpoints by simply providing a base URL and a set of endpoint-specific configurations. It abstracts away much of the boilerplate code typically required for making HTTP requests.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => 'users',
providesTags: ['Users'],
}),
createUser: builder.mutation({
query: (user) => ({
url: 'users',
method: 'POST',
body: user,
}),
invalidatesTags: ['Users'],
 }),
}),
});


  • Intelligent data fetching and background updates: RTK Query provides intelligent data fetching capabilities, including support for pagination, optimistic updates, and background data refetching. It allows you to easily fetch and update data from APIs while handling common scenarios such as loading states, retries, and pagination.


const { useGetUsersQuery } = api;
function UsersList() {
const { data, isLoading, isError, refetch } = useGetUsersQuery();
if (isLoading) {
return <div>Loading...</div>;
 }

if (isError) {
return <div>Error occurred while fetching users.</div>;
}
return (
<div>
{data.map((user) => (
<div key={user.id}>{user.name}</div>
 ))}
<button onClick={() => refetch()}>Refresh</button>
</div>
 );
}


  • Normalized data structures: RTK Query normalizes API responses by default, ensuring that related entities are stored in a normalized form in the Redux store. This simplifies data manipulation and enables efficient updates and lookups.


const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => 'users',
transformResponse: (response) => normalize(response.users, [userSchema]),
 }),
 }),
});

const userSchema = new schema.Entity('users');
const { useGetUsersQuery } = api;


  • Built-in state management: RTK Query integrates seamlessly with Redux and leverages Redux Toolkit’s powerful state management capabilities. It automatically generates Redux slices for each API endpoint, providing preconfigured actions, reducers, and selectors that can be easily used in your application.


const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
 },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
});

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);


  • TypeScript support: RTK Query is built with TypeScript and provides strong typing throughout its API. It leverages TypeScript’s type inference to provide type-safe access to API data, including response payloads, query parameters, and error handling.


interface User {
id: number;
name: string;
email: string;
}

const { useGetUsersQuery, useCreateUserMutation } = api;
function UsersList() {
const { data, isLoading, isError, refetch } = useGetUsersQuery();
if (isLoading) {
return <div>Loading...</div>;
  }

if (isError) {
return <div>Error occurred while fetching users.</div>;
}

return (
<div>
{data.map((user: User) => (
<div key={user.id}>{user.name}</div>
))}
<button onClick={() => refetch()}>Refresh</button>
</div>
);
}


  • Customization and extensibility: RTK Query offers a flexible and extensible architecture that allows you to customize and override its default behavior. You can define custom hooks, modify cache behavior, intercept requests and responses, and extend the built-in functionality to fit your specific needs.


const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => 'users',
transformResponse: (response) => customTransform(response),
 }),
 }),
});

function customTransform(response) {
return response.data.map((item) => ({ id: item.id, name: item.full_name }));
}


Mindsetshift

When using RTK Query, we are no longer thinking about “managing state” per se. Instead, we now think about “managing cached data.”


Rather than trying to write reducers ourselves, we’re now going to focus on defining “where is this data coming from?”, “how should this update be sent?”, “When should this cached data be re-fetched?”, and “How should the cached data be updated?”


Thank you for reading.