One common misunderstanding I’ve noticed is that people often view React Query primarily as a data-fetching library. I understand this perspective, as it is indeed the most common use case for the library. However, if you take a look at the React Query home page, you will notice that the main description for React Query from its maintainers is “Powerful asynchronous state management” and the library doesn’t care about asynchronous implementation details as long as you return a promise.
With that in mind, we can easily see that the title of this article is somehow incorrect. So instead of trying to find a way to refresh your auth token with React Query, you should focus on the data fetching implementation. In most cases, people tend to use axios
, simple fetch
, or any other library to fetch data from their API. So we should focus on how to refresh our auth token with those libraries instead of React Query.
For this article, we will use axios
as our data-fetching library with React Query. However, the same concept applies to any other library. Even though using axios
is highly recommended as it has built-in support for interceptors, which we will use to refresh our authentication token.
As part of this article, we will also cover how to write unit tests for our implementation. Make sure to read the whole article to get the most out of it.
Table of Content
Simple Project Setup
To simplify and focus on the main topic, let’s assume we have a basic app that retrieves user data from an API and displays it in the UI.
As you can see, we have 3 files:
client.tsx
: ouraxios
instance that we will use to fetch data from the API.use-me.tsx
: a customuseMe
hook, which usesuseQuery
from React Query to fetch the user from the API.app.tsx
: our main component that uses theuseMe
hook to display the user name through theUserView
component.
This setup is pretty simple, but to some extent, it’s mainly how most production apps are structured. Now, let’s see how we can implement the auth token refresh logic.
Refresh auth token implementation
As you may gather from the introduction and to prove my point, we will not touch any React Query code here. Instead, all the work will be done in the client file src/client.tsx
.
The first step is to create two new functions to set the token header and remove it from our axios
instance.
Normally, those two functions should be called depending on the user’s authentication state. On login, and as we have the token from the API, we need to set it in the headers of the client
instance. On logout, we need to remove it.
Now, we need to add a simple library named axios-auth-refresh
to refresh our authentication token. This library is a basic axios
interceptor that will be triggered whenever we receive a 401 error from the API. This library requires a refresh token function that we need to implement.
To implement the refreshAuth
function, we need to create a new file called refresh-auth.ts
when we mainly :
- Fetch the new token from the API.
- On success, we set the new token in the failed request headers so that it can be retried. Additionally, we also need to set the new token in our Axios instance for future requests.
- On failure, that means we can’t get a new token from the API. We can return the error and probably redirect the user to the login page, for example, in such cases.
That’s it! Now, whenever we receive a 401 error from the API, the refreshAuth
function will be triggered, and we will try to fetch a new token from the API. If we succeed, we will set the new token in the failed request headers and to our axios
client instance and try again. If we are unable to obtain a new token, we return an error and redirect the user to the login page.
We can end this article here, but let’s be a good developer and try to add some unit tests to make sure that the code works as expected.
Write Unit tests
Writing unit tests for a React application may require a whole article, but we will try to keep it simple here, and by the end, I will share you with a link to the complete code so you can explore it further.
As a wise person once said, “Don’t test the implementation, test use cases.” So let’s forget about how we implement this and figure out what are the use cases:
-
When everything is fine and we receive a 200 response from the API with user details, the
refreshAuth
function shouldn’t be called (happy path). -
When sending a request to the API without a header token, we should not call the
refreshAuth
function and should return an error. This assumes that the token header is required to obtain a new token from the API. -
If the API returns an error other than 401, we should not call the
refreshAuth
function, and our hook will return the error.(e.g., 500 error) -
When we receive a 401 error from the API, we should call the
refreshAuth
function, retry the request, and return the user details. -
When we receive a 401 error from the API and are unable to obtain a new token, we should return an error.
We can make sure all those use cases are working as expected by just testing the useMe
hook as it’s our main entry point to the API. So let’s get started.
To test the useMe
hook, we are going to use react-testing-library
with jest
to test the useMe
hook and nock
to mock the API. I assume that you are familiar with those libraries. If not, you can check the links above to learn more about them, or you can comment below if you think I should write more articles about testing.
First, let’s prepare a react-query
wrapper for our hook. The only important part is that we need to disable retries for our tests. More details can be found here.
Next is to prepare our mock API. We will use nock
to mock the API and return the expected response for each use case.
As you may have noticed in the code below; we create multiple mocks for the same API to handle different scenarios. This allows us to easily test various situations without the need to repeatedly write the same code. For example, we can call the mockUserAPI('success')
to mock a successful response from the API and mockUserAPI('error')
to mock an error response.
Now, we can write our tests for our use cases
As you can see, we wrote 5 tests to cover all the use cases. For each test, we mock the API response based on the use case and then we execute the useMe
hook using renderHook
from react-testing-library
. Finally, we assert that the hook returns the expected result and that the fetchNewToken
function is called when needed.
👉 You can find the complete code on GitHub
That’s it! I hope you enjoyed this article. Make sure to share it with your network so that other people can benefit from it as well. If you have any questions, feel free to comment below, and I will be happy to help.