Аутентифікація та Обробка Помилок
Аутентифікація та Обробка Помилок
Робота з захищеними даними (Authorized only) додає шар складності. Що робити, якщо токен протух? Як глобально показати помилку?
Глобальна Обробка Помилок
Замість того, щоб писати if (isError) у кожному компоненті, ми можемо перехоплювати помилки на рівні QueryCache.
import { QueryCache, MutationCache, QueryClient } from '@tanstack/react-query';
import { toast } from 'react-hot-toast';
export const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
// Ігноруємо помилки, якщо ми їх обробляємо локально (наприклад, у формі)
if (query.meta?.errorMessage) {
toast.error(query.meta.errorMessage);
} else {
toast.error(`Global Error: ${error.message}`);
}
},
}),
mutationCache: new MutationCache({
onError: (error) => {
toast.error(error.message);
},
}),
});
Використання meta:
useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
meta: {
errorMessage: 'Не вдалося завантажити список справ',
},
});
JWT та Refresh Token
Це класична проблема. У вас є короткоживучий accessToken і довгоживучий refreshToken.
Коли accessToken вмирає, сервер повертає 401.
Вам потрібно:
- Перехопити 401.
- Поставити всі інші запити на паузу.
- Зробити запит на
/refresh. - Оновити токен.
- Повторити оригінальний запит.
Це НЕ завдання TanStack Query. Це завдання вашого HTTP-клієнта (Axios).
Axios Interceptor Pattern
TanStack Query нічого не знає про токени. Він просто викликає функцію. Якщо функція кидає помилку — він переходить в status: error.
Але якщо Axios "тихо" оновить токен і повторить запит, TanStack Query навіть не помітить, що була помилка. Це ідеальний сценарій.
import axios from 'axios';
const api = axios.create({ baseURL: '/api' });
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// Якщо 401 і це не повторна спроба
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const { data } = await axios.post('/api/refresh-token');
// Оновлюємо токен в хедерах
api.defaults.headers.common['Authorization'] = `Bearer ${data.accessToken}`;
originalRequest.headers['Authorization'] = `Bearer ${data.accessToken}`;
// Повторюємо оригінальний запит
return api(originalRequest);
} catch (refreshError) {
// Якщо refresh теж впав — юзер має залогінитись заново
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
Відключення запитів без токена
Якщо у вас немає токена (наприклад, юзер не залогінений), не треба навіть намагатися робити запити.
const { accessToken } = useAuth();
useQuery({
queryKey: ['user'],
queryFn: fetchUser,
// Запит полетить тільки якщо є токен
enabled: !!accessToken,
});
Logout: Очищення Кешу
Коли користувач виходить з системи, критично важливо очистити кеш. Інакше наступний користувач може побачити дані попереднього (якщо вони сядуть за один комп'ютер).
const logout = () => {
// 1. Видаляємо токен
localStorage.removeItem('token');
// 2. Очищаємо весь кеш
queryClient.clear();
// Або видаляємо конкретні запити
// queryClient.removeQueries();
};
queryClient.clear() видаляє всі дані, але не зупиняє активні запити.
queryClient.resetQueries() скидає дані до початкового стану (і може спровокувати refetch).
Для логауту найкраще clear() або removeQueries().