Integrate API’s in React and Redux with ease

React and Redux with RTK Query

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.

2 comments

Leave a Reply

Your email address will not be published. Required fields are marked *