When building a frontend for a web application in React and Redux it can be really tedious to integrate the backend API into it. You have to integrate a http library like axios, write the code to do the requests and much more. In your components you’ll call the requests and you have to keep track of the status of the requests. This creates a lot of boilerplate code, which can be really annoying. To combat this the folks at Redux created a nice tool called RTK Query.
What is RTK Query?
RTK Query is part of the Redux Toolkit. It’s a tool specifically created for fetching and caching data. It is built on top of other Redux Toolkit components like createAsyncThunk
and createSlice
. This is what makes it so puwerful.
The API it exposes is really easy to use and removes a lot of the boilerplate code that was necessary in the past. For example when you want to fetch the data of the current user you had to do it like this.
import axios from 'axios';
export default function UserMenu() {
const [user, setUser] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(undefined);
useEffect(() => {
if (!user) {
setIsLoading(true);
axios.get('/api/users/me')
.then(response => {
setUser(response.data);
setIsLoading(false);
})
.catch(error => {
setError(error);
setIsLoading(false);
});
}
});
return (
<div className="menu">
{isLoading && <Loader />}
{!isLoading && !error && <span className="username">{user.name}</span>}
</div>
)
}
Code language: JavaScript (javascript)
As you can see in the example you’ll have to manage the loading and error states by yourself. With RTK Query you don’t have to do this. Let me explain that with the same example as above, but this time with RTK Query.
import {useGetMeQuery} from './redux/api';
export default function UserMenu() {
const {data: user, isLoading, error} = useGetMeQuery();
return (
<div className="menu">
{isLoading && <Loader />}
{!isLoading && !error && <span className="username">{user.name}</span>}
</div>
)
}
Code language: JavaScript (javascript)
Now doesn’t this look a lot cleaner? You get the same functionality as the example above, but it much cleaner. And it gets even better.
Setup RTK Toolkit
It comes installed with the Redux Toolkit library. To install this toolkit simple install @reduxjs/toolkit
using your favorite package manager like yarn
or npm
. I’m going to assume you’ve setup your Redux Store already, so I’ll skip this part. Now we can implement our API using the createApi
function provided by the RTK Query tool. Let’s create our user API:
import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: 'https://example.org/',
prepareHeaders: (headers, {getState}) => {
const token = getState().auth.token;
if (token) {
headers.set('Authorization', `Bearer ${token}`)
}
return headers;
}
}),
endpoints: build => ({
getMe: build.query({
query: () => (`/api/users/me`),
}),
createUser: build.mutation({
query: (request) => ({
url: '/api/users',
method: 'POST',
body: request,
}),
}),
}),
});
export const {useGetMeQuery, useCreateUserMutation} = api;
export default api;
Code language: JavaScript (javascript)
It doesn’t take much to define an API in RTK Query. We have to define the baseUrl
and for security we’ve added an extra header using prepareHeaders
which contains the bearer token. This callback has access to the state of your Redux store, so we can fetch the JWT Token from it.
The next part is the definition of the endpoints
. We’ve added support for the /api/users/me
endpoint which returns the user details of the owner of the JWT Token and a createUser
endpoint, which, like the name implies creates a new user.
Then we export the react hooks created by the createApi
function, which we can use in our application. And lastly we export the api
object.
To make this all work we need to register our API to the Redux Store, so let’s make this happen!
import {configureStore} from '@reduxjs/toolkit';
import api from './api';
import authReducer from './slices/auth';
// Define all reducers
const reducer = {
[api.reducerPath]: api.reducer,
auth: authReducer,
};
export const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware),
devTools: process.env.NODE_ENV !== 'production',
});
Code language: JavaScript (javascript)
First we need to add the reducer created by the createApi
function. This will make sure the data from the API calls will be stored in the Redux store. The next change we’ve made is to add the middleware the createApi
function created to the store using middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware)
. This will make sure the advanced features of RTK Query like caching and such works correctly.
Code splitting
When you have an API with a lot of endpoints, your api.js
file will become pretty large and a nightmare to maintain. To remedy this RTK Query made it possible to inject endpoints in an existing API object. This makes the files a lot smaller and easier to maintain.
Let start with the base api.ts
file into which we can inject the extra endpoints from the other files.
import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react'
// Initialize an empty api service that we'll inject endpoints into later as needed
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: 'https://example.org',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('Authorization', `Bearer ${token}`)
}
return headers;
}
}),
endpoints: () => ({}),
});
export default api;
Code language: JavaScript (javascript)
It looks pretty much the same as the other example, but we initialized the endpoints with an empty object. We’ll inject the endpoints later in the different files we’ll create.
import api from "./api";
const usersApi = api.injectEndpoints({
endpoints: build => ({
getMe: build.query({
query: () => (`/api/users/me`),
}),
createUser: build.mutation({
query: (request) => ({
url: '/api/users',
method: 'POST',
body: request,
}),
}),
}),
});
export const { useGetMeQuery, useCreateUserMutation } = usersApi;
export default usersApi;
Code language: JavaScript (javascript)
The object returned from the createApi
function has an injectEndpoints
method which we’ll use in our ‘API Extension points’. In the injectEndpoints
method we can simply add the endpoints we want.
Because we’ve already added the base API to our Redux store, there is no need to add extra reducers and middleware for our injected endpoints. When we want to load the current user we can simply use the useGetMeQuery
from the usersApi.ts
file above. This works the same as before.
Extra options
I haven’t even touched the surface of all the capabilities of the RTK Query tool. There are many more options for caching the data, refetching when needed and many more. I’ll write another article about this in the future. For now check out their official documentation for the information.
Not working
through an error 401 (Unauthorized)
Hi Basit,
On which endpoint do you get the 401 (Unauthorized)?