You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 06:35:26