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:
Together, createApi and fetchBaseQuery help you set up the foundation for making API requests and handling responses in RTK Query.
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'],
}),
}),
});
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>
);
}
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;
const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
});
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
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>
);
}
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 }));
}
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.