Using RTK Query with GraphQL

Redux Toolkit and the included RTK Query are awesome. Not only are both packages incredibly well designed, easy to use and production-ready, but they also provide tons of documentation.

However, I had trouble finding code examples for certain GraphQL use-cases. In the end I pieced things together, so I decided to document my results here.

This uses @reduxjs-toolkit: 1.8.1 and @rtk-query/graphql-request-base-query: 2.2.0. Consult the docs to see if this information is now outdated.

Using the base query

If you want to use GraphQL the official documentation quickly points you to a custom build graphqlBaseQuery. But RTK Query actually provides a more sophisticated GraphQL query: graphqlRequestBaseQuery. That one is used in some of the sandbox code examples, too.

import { createApi } from "@reduxjs/toolkit/query/react";
import { graphqlRequestBaseQuery } from "@rtk-query/graphql-request-base-query";

export const api = createApi({
    reducerPath: "api",
    baseQuery: graphqlRequestBaseQuery({
        url: "https://api.acme.com/graphql/",
    }),
    endpoints: () => ({}),
});

If your api requires a token to access, you can pass the token via the prepareHeaders param.

import { createApi } from "@reduxjs/toolkit/query/react";
import { graphqlRequestBaseQuery } from "@rtk-query/graphql-request-base-query";

export const api = createApi({
    reducerPath: "api",
    baseQuery: graphqlRequestBaseQuery({
        url: "https://api.acme.com/graphql/",
        prepareHeaders: (headers, { getState }) => {
            // Retrieve token from redux store
            const token = getState().auth?.token;

            if (token) {
                headers.set("authorization", `Bearer ${token}`);
            } else {
                // use refresh token or navigate to login
            }
            return headers;
        },
    }),
    endpoints: () => ({}),
});

I store that token in another api-splice (e.g. auth-slice). RTK Query gives you an easy way to access other slices from with-in the prepareHeaders function.

Dynamically changing the API url

A React Native app that I was working on required me to dynamically change between our production and staging API (for testing).

Luckily, RTK Query supports this use-case and even provides an example for a fetch based API. The changes are trivial. It's almost plug and play to get it to work with GraphQL.

import { DocumentNode } from "graphql";
import { ClientError } from "graphql-request";
import type { BaseQueryFn } from "@reduxjs/toolkit/query/react";
import { graphqlRequestBaseQuery } from "@rtk-query/graphql-request-base-query";

const dynamicGraphqlBaseQuery: BaseQueryFn<
    {
        document: string | DocumentNode;
        variables?: any;
    },
    unknown,
    unknown,
    Partial<Pick<ClientError, "request" | "response">>,
    {}
> = async (args, api, extraOptions) => {
    const baseUrl = api.getState().config.env.url;
    const rawBaseQuery = graphqlRequestBaseQuery<Partial<ClientError>>({
        url: `${baseUrl}/graphql`,
        // can also drop prepareHeaders here
    });
    return rawBaseQuery(args, api, extraOptions);
};

export const api = createApi({
    reducerPath: "api",
    baseQuery: dynamicGraphqlBaseQuery,
    endpoints: () => ({}),
});

Like with our token earlier, the necessary url is stored in a config-slice. And just like with prepareHeaders, we can use the second parameter to access getState (and retrieve our url from the redux state).