I have a set of API calls written with Redux Toolkit. For example:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { Contacts } from '../datamodel/Contact';
import { getAccessToken } from '../util/userContextManagement';
export const contactsApi = createApi({
reducerPath: 'contactsApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api/users/current' }),
endpoints: builder => ({
getContacts: builder.query<Contacts, void>({
query: () => {
return ({
url: '/contacts',
method: 'GET',
headers: { Authorization: `Bearer ${getAccessToken()?.token}`}
});
},
})
})
})
export const { useGetContactsQuery } = contactsApi
I am able to inject the access token using a function: getAccessToken()
.
However, I'd like to detect in the function that the access token has expired and refresh it with another API call before the function returns.
Unfortunately, I am not able to do this in this function, because getAccessToken()
isn't react hook.
export const getAccessToken = () => {
const [trigger] = useLazyGetRefreshTokensQuery();
(...)
return getUserContext().tokens.find(t => t.type === TokenTypeEnum.ACCESS_TOKEN)
}
I am getting:
React Hook "useLazyGetRefreshTokensQuery" is called in function "getAccessToken" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"
How could I refresh the token in RTK Query?
First, I don't know if you can use any hook in your API. You might have to change the way to get the token by:
useGetAccessToken
? The error states React Hook names must start with the word "use"
. This could fix the issue of retrieving the token.For refetching tokens,
You could try using the baseQueryWithReauth
mentioned in the docs, where if the token
returns 401
a call to your refreshToken
method is made and you can store your updated tokens and retry the request.
It would look something like this in your implementation:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query'
import { Contacts } from "../datamodel/Contact";
import { getAccessToken } from "../util/userContextManagement";
const baseQuery = fetchBaseQuery({
baseUrl: '/api/users/current',
prepareHeaders: (headers) => {
// this method should retrieve the token without a hook
const token = getAccessToken();
if (token) {
headers.set("authorization", `Bearer ${token}`);
}
return headers;
},
});
const baseQueryWithReauth: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
let result = await baseQuery(args, api, extraOptions);
if (result.error && result.error.status === 401) {
// try to get a new token
const refreshResult = await baseQuery("/refreshToken", api, extraOptions);
if (refreshResult.data) {
// store the new token in the store or wherever you keep it
api.dispatch(tokenReceived(refreshResult.data));
// retry the initial query
result = await baseQuery(args, api, extraOptions);
} else {
// refresh failed - do something like redirect to login or show a "retry" button
api.dispatch(loggedOut());
}
}
return result;
};
export const contactsApi = createApi({
reducerPath: "contactsApi",
baseQueryWithReauth,
endpoints: (builder) => ({
getContacts: builder.query<Contacts, void>({
query: () => {
return {
url: "/contacts",
method: "GET",
};
},
}),
}),
});
export const { useGetContactsQuery } = contactsApi;