React中useNavigate与Axios拦截器触发无效Hook调用错误排查
解决React中Invalid Hook Call错误的方案
错误原因
你在普通函数getAllDocuments里直接调用了自定义HookusePrivateAxios,违反了React Hook的核心规则:Hook只能在函数组件的顶层或其他自定义Hook内部调用,不能在普通函数、循环、条件语句中使用。
解决方案
方案1:在组件内使用Hook并调用API
把API调用逻辑移到函数组件内部,先通过Hook获取Axios实例,再发起请求:
// 示例组件 import usePrivateAxios from '../usePrivateAxios'; function DocumentsPage() { const privateAxios = usePrivateAxios(); const fetchDocuments = async () => { try { const data = await privateAxios.get('/documents').then(res => res.data); // 处理返回的数据 } catch (err) { // 处理请求错误 } }; return ( <div> <button onClick={fetchDocuments}>获取文档列表</button> </div> ); }
同时修改documentAPI.js,让它接收Axios实例作为参数:
export const getAllDocuments = (privateAxios, config) => { return privateAxios .get("/documents", config) .then((response) => response.data) .catch((error) => { throw error; }); };
在组件内调用时:
const data = await getAllDocuments(privateAxios);
方案2:重构私有Axios实例,移除Hook依赖
如果不想在组件内处理拦截器逻辑,可以直接给Axios实例配置拦截器,用window.location实现跳转(替代useNavigate):
修改privateAxios.js:
import { axiosPrivate } from "./axios"; // 配置请求拦截器 axiosPrivate.interceptors.request.use( (config) => { const accessToken = localStorage.getItem("accessToken"); if (!accessToken) { // 直接跳转登录页 window.location.href = "/admin/login"; } else { config.headers.Authorization = `Bearer ${accessToken}`; } return config; }, (error) => Promise.reject(error), ); // 配置响应拦截器 axiosPrivate.interceptors.response.use( (response) => response, (error) => Promise.reject(error), ); export default axiosPrivate;
修改documentAPI.js直接导入使用:
import privateAxios from "../privateAxios"; export const getAllDocuments = (config) => { return privateAxios .get("/documents", config) .then((response) => response.data) .catch((error) => { throw error; }); };
方案3:自定义Hook返回封装好的API请求函数
创建一个Hook,直接返回封装后的API请求方法,在组件里调用Hook获取方法后使用:
// useDocumentAPI.js import { axiosPrivate } from "./axios"; import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; const useDocumentAPI = () => { const navigate = useNavigate(); useEffect(() => { const requestInterceptor = axiosPrivate.interceptors.request.use( (config) => { const accessToken = localStorage.getItem("accessToken"); if (!accessToken) { navigate("/admin/login"); } else { config.headers.Authorization = `Bearer ${accessToken}`; } return config; }, (error) => Promise.reject(error), ); const responseInterceptor = axiosPrivate.interceptors.response.use( (response) => response, (error) => Promise.reject(error), ); return () => { axiosPrivate.interceptors.request.eject(requestInterceptor); axiosPrivate.interceptors.response.eject(responseInterceptor); }; }, [navigate]); const getAllDocuments = (config) => { return axiosPrivate .get("/documents", config) .then((res) => res.data) .catch((err) => { throw err; }); }; return { getAllDocuments }; }; export default useDocumentAPI;
在组件里使用:
import useDocumentAPI from './useDocumentAPI'; function DocumentsPage() { const { getAllDocuments } = useDocumentAPI(); const fetchData = async () => { try { const data = await getAllDocuments(); // 处理数据 } catch (err) { // 处理错误 } }; return <button onClick={fetchData}>获取文档</button>; }
关键注意点
- 所有自定义Hook必须以
use开头,且只能在函数组件或其他自定义Hook的顶层调用 - 非组件环境中实现页面跳转,优先使用
window.location或提前创建React Router的history实例
内容的提问来源于stack exchange,提问作者Văn Thuận Nguyễn
相关产品推荐
相关产品推荐

