Skip to content

How to trigger a global loading state in RTK query requests in Typescript

What's RTK query?

RTK query is a data fetching and caching solution that helps you reduce the code needed to make requests to external services, like our backend or other places. RTK query is an optional addon included in the Redux Toolkit package

Implementation

In this tutorial we are supposing you already have a React project running and you already added RTK query to your Redux Toolkit store, we are going to show you an example of how you can trigger a global loading state when fetching data from RTK query requests

// Import RTK query types
import { FetchBaseQueryArgs } from "@reduxjs/toolkit/dist/query/fetchBaseQuery";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

/**
 * Action that changes loading, a variable in main slice used to display a loading component on the whole application,
 * you can import the action you use to change your loading state
 */
import { setLoading } from "../../store/main";

/**
 * The base url where our RTK query request will be done
 */
const commonQueryOptions: FetchBaseQueryArgs = {
    baseUrl: `http://localhost:3010/`,
};

/**
 * @desc this function will handle changing our loading state and handling success or errors in our requests
 * @param dispatch dispatch method from the store, to be able to dispatch actions on slices
 * @param queryFulfilled promise that will resolve once the request finishes
 * @param showLoading boolean to trigger the loading state or not
 * @param showSuccess boolean to trigger success message
 * @param showError boolean to trigger error message
 */
const commonOnQueryStarted = async (
    { dispatch, queryFulfilled },
    showLoading = false,
    showSuccess = false,
    showError = false,
) => {
    try {
        /**
         * If we want to show our global loading state then we will dispatch an action to set the state to true once the
         * request starts
         */
        if (showLoading) {
            dispatch(setLoading(true));
        }

        /**
         * wait for the request to be resolved
         */
        await queryFulfilled;

        /**
         * If we want to show our global loading state then we will dispatch an action to set the state to false once the
         * request finishes
         */
        if (showLoading) {
            dispatch(setLoading(false));
        }

        /**
         * Here you can also handle success requests to show the user a modal or toast with success message
         */
        if (showSuccess) {
            // Do something
        }
    } catch (err) {
        /**
         * If the request fails or something happens, then we will hide the loading state
         */
        if (showLoading) {
            dispatch(setLoading(false));
        }

        /**
         * Here you can also handle errors to show a global modal or toast to let the user know what happened
         */
        if (showError) {
            // Do something
        }
    }
};

const baseQuery = fetchBaseQuery({ ...commonQueryOptions });

export const exampleAPI = createApi({
    reducerPath: "api",
    baseQuery,
    endpoints: (builder) => ({
        // Do post request and show loading state
        exampleMutationLoading: builder.mutation<any, any>({
            query: (body) => ({
                url: `example-mutation`,
                method: "POST",
                body,
            }),
            // Do get request and show loading state
            async onQueryStarted(params, { dispatch, queryFulfilled }) {
                // Call commonOnQueryStarted to show loading when this request is triggered
                await commonOnQueryStarted({ dispatch, queryFulfilled }, true);
            },
        }),
        exampleQueryLoading: builder.query<any, any>({
            query: () => ({
                url: `example-query`,
                method: "GET",
            }),
            // Call commonOnQueryStarted to show loading when this request is triggered and show message if the request was resolve successfully
            async onQueryStarted(params, { dispatch, queryFulfilled }) {
                await commonOnQueryStarted({ dispatch, queryFulfilled }, true, true);
            },
        }),
        // Do get request and don't show loading state
        exampleQueryNoLoading: builder.query<any, any>({
            query: () => ({
                url: `example-query`,
                method: "GET",
            }),
        }),
    }),
});

export const {
    useExampleMutationLoadingMutation,
    useExampleQueryLoadingQuery,
    useExampleQueryNoLoadingQuery,
} = exampleAPI;

Conclusion

Now we just need to create a component that uses the loading value on the main slice to show or hide our loading component. We can call commonOnQueryStarted inside onQueryStarted to trigger our loading state and handle success or errors, this gives us flexibility to handle request differently depending on what we want to do, also it triggers our loading state from a single place so when we want to change or update the code we just need to change this file instead of searching all the places were we dispatched the loading state