如何在Axios拦截器中正确使用useNavigate?解决无效Hook调用报错
问题场景
我为服务创建了Axios响应拦截器,想在特定API调用失败(比如500状态)时跳转到错误组件,但运行时抛出错误:Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
相关代码
agent.ts
axios.defaults.baseURL = "https://localhost:5001/api/"; const responseBody = (response: AxiosResponse) => response.data; axios.interceptors.response.use( (response) => { return response; }, (error: AxiosError) => { const { data, status }: { data: any; status: any } = error.response!; const navigate = useNavigate(); switch (status) { case 500: //toast.error(data.title); navigate("server-error"); break; default: break; } return Promise.reject(error.response); } ); const request = { get: (url: string) => axios.get(url).then(responseBody), }; const Catalog = { list: () => request.get("products"), }; const agent = { Catalog, };
组件调用代码
useEffect(() => { agent.Catalog.list().then((products) => setProducts(products)); }, []);
错误原因
useNavigate是React Router提供的Hook,必须在函数组件的顶层作用域或者自定义Hook内部调用。而Axios拦截器是在组件外部定义的普通函数,不属于React的组件渲染流程,直接在这里调用Hook违反了React的Hook规则,所以触发了报错。
解决方案
方案一:组件内处理错误导航
把导航逻辑移到请求调用的catch回调中,同时简化Axios拦截器的职责:
- 修改agent.ts的拦截器,只负责传递错误:
axios.defaults.baseURL = "https://localhost:5001/api/"; const responseBody = (response: AxiosResponse) => response.data; axios.interceptors.response.use( (response) => response, (error: AxiosError) => { // 仅传递错误,导航逻辑移至组件内处理 return Promise.reject(error.response); } ); const request = { get: (url: string) => axios.get(url).then(responseBody), }; const Catalog = { list: () => request.get("products"), }; const agent = { Catalog, };
- 在组件中处理错误并导航:
import { useNavigate } from "react-router-dom"; // 组件内部 const navigate = useNavigate(); useEffect(() => { agent.Catalog.list() .then((products) => setProducts(products)) .catch((error) => { if (error?.status === 500) { navigate("server-error"); } }); }, [navigate]);
方案二:封装自定义Hook统一处理错误
如果需要全局统一的错误导航逻辑,可以封装一个自定义Hook,既遵守Hook规则又复用逻辑:
- 创建自定义Hook
useAgent.ts:
import { useNavigate } from "react-router-dom"; import agent from "../agent"; export const useAgent = () => { const navigate = useNavigate(); // 统一错误处理函数 const handleError = (error: any) => { if (error?.status === 500) { navigate("server-error"); } return Promise.reject(error); }; // 封装带错误处理的请求方法 const Catalog = { list: () => agent.Catalog.list().catch(handleError), }; return { Catalog }; };
- 在组件中使用该Hook:
import { useAgent } from "./hooks/useAgent"; // 组件内部 const { Catalog } = useAgent(); useEffect(() => { Catalog.list().then((products) => setProducts(products)); }, []);
方案三:使用历史对象(适配React Router v5或全局导航场景)
如果使用React Router v5,或者必须在非组件环境实现导航,可以创建独立的history实例:
- 创建
history.ts文件:
import { createBrowserHistory } from "history"; export const history = createBrowserHistory();
- 在agent.ts中导入并使用:
import { history } from "./history"; axios.interceptors.response.use( (response) => response, (error: AxiosError) => { const { status } = error.response!; if (status === 500) { history.push("server-error"); } return Promise.reject(error.response); } );
注意:React Router v6官方推荐使用useNavigate,此方案更适合v5或特殊全局导航需求。
内容的提问来源于stack exchange,提问作者Asdf1234567
相关产品推荐
相关产品推荐

