What stack that I use?
"swr": "^2.2.5",
"next": "14.2.3",
Problem
SPA (Single Page Application) are hard to identify user’s session using JWT. In my case, I use JWT as token session that used for every interaction API.
Solution
We can use onErrorRetry
property in swr documentation. Here is my code to automatically refresh token when API return 401
and token_expired
code.
"use client";
import { SWRConfig } from "swr";
import { FetcherErrorException, fetcherSwrGet } from "./fetcher";
export function SwrConfig({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
errorRetryCount: 3,
async onErrorRetry(err: FetcherErrorException, key, config, revalidate, { retryCount }) {
if (retryCount >= 3) return;
if (err.status === 404) return;
// My server return spesific code to identify expired token
// Verify here
if (err.response?.code === "token_expired") {
try {
// add your function to refresh token
await refreshtoken();
} catch (e: any) {
console.error(e)
if (e.status === 401) {
// if error 401, do force logout and redirect to login
logout()
window.location.href = "/auth/login"
return
}
}
// revalidate the request, ask swr to re-fetch the API
revalidate({ retryCount: retryCount + 1 })
}
}
}}
>
{children}
</SWRConfig>
);
}
You must return status
in error class and response
json also, here is my fetcher API looks like
export const fetchApi = async (...params: Parameters<typeof fetch>) => {
const token = useUserAuth.getState().token;
params[0] = new URL(params[0] as string, process.env.NEXT_PUBLIC_APP_SERVER);
const header = new Headers();
header.set('Content-Type', 'application/json');
params[1] = Object.assign(params[1] || {}, {
headers: header
})
if (token?.accessToken) {
header.set('Authorization', `Bearer ${token.accessToken}`)
}
const resFetch = await fetch(...params);
const resFetchJson = await resFetch.json();
// --------- Focus in this part ---------
if (!resFetch.ok) {
const message = resFetchJson?.message || resFetch.statusText;
throw new FetcherErrorException(message, {
response: resFetchJson,
status: resFetch.status,
})
}
return resFetchJson;
}
export class FetcherErrorException extends Error {
status: number;
response: any
constructor(message: string, options: {
status: number
response: any
}) {
super(message)
this.status = options.status
this.response = options.response
}
}