️(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/'; import { ConfigProvider } from './config/';
export const DEFAULT_QUERY_RETRY = 1;
/** /**
* QueryClient: * QueryClient:
* - defaultOptions: * - defaultOptions:
@@ -19,7 +21,7 @@ import { ConfigProvider } from './config/';
const defaultOptions = { const defaultOptions = {
queries: { queries: {
staleTime: 1000 * 60 * 3, staleTime: 1000 * 60 * 3,
retry: 1, retry: DEFAULT_QUERY_RETRY,
}, },
}; };
const queryClient = new QueryClient({ const queryClient = new QueryClient({

View File

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

View File

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