1

We are using react-query to fetch data from an API. For every api call there is a seperate hook with different query key but all of them are using same function callApi() which takes the api url as parameter to fetch data.

Inside callAPI() hook we have the logic to get the auth token to be used from authState. (Code below)

const useAxios = (baseUrl = config.API_URL) => {
    const history = useHistory()
    const [{ accessToken }] = useAuthState()

    const callApi = useCallback(
        async ({ headers, ...rest }) => {
            try {
                const { data } = await axios({
                    baseURL: baseUrl,
                    headers: {
                        'Content-Type': 'application/json',
                        Authorization: `Bearer ${accessToken}`,
                        ...headers,
                    },
                    ...rest,
                    validateStatus: (status) => status >= 200 && status <= 299,
                })
                return data
            } catch (err) {
                if (err && err.response && err.response.status === 401) {
                    history.push(`${appConfig.APP_SUBPATH}/sessionExpired`)
                } else if (err && err.response && err.response.status === 503) {
                    history.push(`${appConfig.APP_SUBPATH}/underMaintenance`)
                }
                throw err
            }
        },
        [accessToken],
    )

    return callApi
}
export default useAxios 
export const useGetItems = () => {
    const callApi = useAxios()

    return useQuery(ALL_ITEMS_KEY, () =>
        callApi({
            method: 'GET',
            url: 'api/v1/items',
        }).then((data) => data.data),
    )
}

Problem : After the token is refreshed react-query still uses the old token to refetch invalidated queries which results in 401 UnAuthorized although the token in state was also updated. I tried from react-query dev tools

Why react-query is still using the old token even though it was refreshed? How can I prevent react-query from using an old token ?

EDIT: I understand that react-query won't automatically refetch if token is updated, but it does if the query is marked invalid as shown below. In that case it uses the old expired token which was used when call was first made

queryCache.invalidateQueries((x) => x.queryKey.includes(ALL_ITEMS_KEY))
Faizan khan
  • 185
  • 1
  • 12

2 Answers2

0

You don't have the access token in the queryKey:

useQuery(ALL_ITEMS_KEY)

so ReactQuery has no way of understanding it should refetch the data when it changes. Add it to the query key:

useQuery(
  [...ALL_ITEMS_KEY, accessToken],
  () => callApi(...),
  { enabled: !!accessToken }, // optional?
)

optionally, you can also disable the query if there is no token.

Jakub Kotrs
  • 5,823
  • 1
  • 14
  • 30
  • What if i mark the query invalid ? In that case it automatically fetches but with an old token – Faizan khan Apr 06 '23 at 21:21
  • Depends on how (where and when) you do that. – Jakub Kotrs Apr 06 '23 at 21:34
  • For example after token is refreshed I create a new item, in that case i would like to fetch the list again so i mark query with "ALL_ITEMS_KEY" as key invalid which results in refetching from the api but with an old token. Shouldn't it fetch again with new token ? – Faizan khan Apr 06 '23 at 21:58
  • No, in that case, React Query refetches with the original `queryFn`, meaning the one with stale token because of stale closures. You should put the token to `queryKey`. – Jakub Kotrs Apr 07 '23 at 16:03
0

You're suffering from a stale closure problem in React, that happens because your reactive dependency (the token) is not part of the queryKey.

When a refetch happens, the queryFunction is used from the time when the key last changed, so it still sees the old token from the closure.

That's why it's so important to include all dependencies inside the queryKey.

I have a 2-part blopost series on closures in general, maybe that's helpful to understand the problem better:


Now tokens are a "special" situations, I wouldn't want to include them in the QueryKey. The question is more: Why does the token come from useAuthState ? I guess that is react context, but why would the token need to be stored in react context in the first place? From react query perspective, there is no need to inform / re-render your component with a new token, unless you also include that token in the queryKey.

A better place to handle tokens would be, sine you are already using axios, an axios interceptor.

TkDodo
  • 20,449
  • 3
  • 50
  • 65
  • In case of using axios interceptor to send the token we would be sending token with every request even 3rd party requests also ? In that case we should add conditional checks in interceptor ? What if we take token out of react context (authContext) and use directly from oidc-client's userManager. Would that solve the or react-query will always use the stale version if the query-key is not changed ? – Faizan khan Apr 11 '23 at 12:19