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).