️(frontend) prevent authentication retry on 401 responses

Stop retry attempts when receiving 401 Unauthorized from /me endpoint since
this clearly indicates authentication status. The original purpose of the /me
call is simply to determine if user is authenticated, and a 401 provides
sufficient information.

Prevents unnecessary network requests caused by React Query's automatic retry
behavior when re-raising exceptions, which was hiding the 401 status. Improves
performance and reduces server load during authentication failures.
This commit is contained in:
lebaudantoine
2025-05-21 16:19:34 +02:00
committed by Anthony LC
parent ff8275fb4e
commit 2a7ffff96d
3 changed files with 23 additions and 6 deletions

View File

@@ -9,6 +9,8 @@ import { useResponsiveStore } from '@/stores/';
import { ConfigProvider } from './config/';
export const DEFAULT_QUERY_RETRY = 1;
/**
* QueryClient:
* - defaultOptions:
@@ -19,7 +21,7 @@ import { ConfigProvider } from './config/';
const defaultOptions = {
queries: {
staleTime: 1000 * 60 * 3,
retry: 1,
retry: DEFAULT_QUERY_RETRY,
},
};
const queryClient = new QueryClient({

View File

@@ -1,9 +1,12 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { DEFAULT_QUERY_RETRY } from '@/core';
import { User } from './types';
type UserResponse = User | null;
/**
* Asynchronously retrieves the current user's data from the API.
* This function is called during frontend initialization to check
@@ -14,8 +17,13 @@ import { User } from './types';
* @throws {Error} Throws an error if the API request fails.
* @returns {Promise<User>} A promise that resolves to the user data.
*/
export const getMe = async (): Promise<User> => {
export const getMe = async (): Promise<UserResponse> => {
const response = await fetchAPI(`users/me/`);
if (response.status === 401) {
return null;
}
if (!response.ok) {
throw new APIError(
`Couldn't fetch user data: ${response.statusText}`,
@@ -28,12 +36,19 @@ export const getMe = async (): Promise<User> => {
export const KEY_AUTH = 'auth';
export function useAuthQuery(
queryConfig?: UseQueryOptions<User, APIError, User>,
queryConfig?: UseQueryOptions<UserResponse, APIError, UserResponse>,
) {
return useQuery<User, APIError, User>({
return useQuery<UserResponse, APIError, UserResponse>({
queryKey: [KEY_AUTH],
queryFn: getMe,
staleTime: 1000 * 60 * 15, // 15 minutes
retry: (failureCount, error) => {
// we assume that a 401 means the user is not logged in
if (error.status == 401) {
return false;
}
return failureCount < DEFAULT_QUERY_RETRY;
},
...queryConfig,
});
}

View File

@@ -22,7 +22,7 @@ export const useSynchronizedLanguage = () => {
);
const changeBackendLanguage = useCallback(
async (language: string, user?: User) => {
async (language: string, user?: User | null) => {
const closestBackendLanguage = getMatchingLocales(
availableBackendLanguages,
[language],
@@ -52,7 +52,7 @@ export const useSynchronizedLanguage = () => {
);
const changeLanguageSynchronized = useCallback(
async (language: string, user?: User) => {
async (language: string, user?: User | null) => {
if (!isSynchronizingLanguage.current) {
isSynchronizingLanguage.current = true;
await changeFrontendLanguage(language);