如何在react-query的onError中使用hooks?Next.js登录跳转需求
你的方案不可行,正确实现思路如下
你直接在QueryClient的onError里使用router的方式是错误的——useRouter是React Hook,必须在组件或自定义Hook的顶层调用,而QueryClient初始化代码不在React组件上下文里,这样写会触发Hook规则报错。
下面是针对Next.js + react-query的全局token过期处理方案:
核心实现逻辑
把token刷新、请求重试的逻辑放在请求库的响应拦截器中(比如axios),跳转登录的逻辑放在React组件上下文里处理,避免违反Hook规则。
1. 用axios拦截器处理token刷新与请求重试
先封装一个带拦截器的axios实例,在拦截器里捕获401错误,尝试刷新token并重试原请求:
// utils/api.js import axios from 'axios'; const api = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, }); // 响应拦截器 api.interceptors.response.use( (res) => res, async (error) => { const originalRequest = error.config; // 仅处理首次401错误(避免无限重试) if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { // 调用刷新token接口 const refreshToken = localStorage.getItem('refreshToken'); const { data } = await api.post('/auth/refresh', { refreshToken }); // 更新本地token与请求头 localStorage.setItem('accessToken', data.accessToken); api.defaults.headers.common['Authorization'] = `Bearer ${data.accessToken}`; // 重试原请求 return api(originalRequest); } catch (refreshError) { // 刷新失败,清除本地token并抛出错误,交给组件处理跳转 localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); return Promise.reject(refreshError); } } return Promise.reject(error); } ); export default api;
2. 在组件中处理登录跳转
Pages Router 场景
直接用Next.js提供的Router对象(无需Hook)在拦截器中跳转:
// 在utils/api.js的catch块中替换跳转逻辑 import Router from 'next/router'; // ...其他代码 catch (refreshError) { localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); Router.push('/login'); return Promise.reject(refreshError); }
App Router 场景
App Router不推荐直接使用Router对象,可创建一个全局错误监听组件,利用useRouter处理跳转:
// components/GlobalAuthHandler.jsx 'use client'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; import { queryClient } from '@/utils/queryClient'; export default function GlobalAuthHandler() { const router = useRouter(); useEffect(() => { // 监听react-query的全局错误 const unsubscribe = queryClient.getQueryCache().subscribe((event) => { if (event.type === 'error') { const error = event.error; // 判断是token刷新失败的401错误 if (error.response?.status === 401 && error.config?._retry) { router.push('/login'); } } }); return unsubscribe; }, [router]); return null; }
然后在根布局中引入这个组件:
// app/layout.js import GlobalAuthHandler from '@/components/GlobalAuthHandler'; export default function RootLayout({ children }) { return ( <html lang="zh-CN"> <body> <GlobalAuthHandler /> {children} </body> </html> ); }
3. 调整react-query配置
关闭react-query的默认重试,避免和拦截器的重试逻辑冲突:
// utils/queryClient.js import { QueryClient } from '@tanstack/react-query'; export const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 0, // 交由拦截器处理重试 onError: (error) => { // 处理其他非token过期的错误 console.error('请求失败:', error); }, }, }, });
内容的提问来源于stack exchange,提问作者Imre_G
相关产品推荐
相关产品推荐

