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))
2
Answers
You don’t have the access token in the queryKey:
so ReactQuery has no way of understanding it should refetch the data when it changes. Add it to the query key:
optionally, you can also disable the query if there is no token.
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.