Axios拦截器调用Auth0 Hook报错:无效Hook调用问题排查
问题解决:Auth0接入后Axios拦截器Hook调用错误
问题场景
使用Auth0登录应用后,调用第三方API获取用户设置时,为请求添加Auth0的access_token后触发错误:
Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
移除拦截器后API可正常调用,但因缺少access_token导致认证失败。
相关代码片段:
登录组件代码
export default observer(function LoginMenu() { const { currentUserStore: { login }} = useStore(); const { loginWithRedirect, isAuthenticated, logout } = useAuth0(); useEffect(() => { if (isAuthenticated) { login() } }, [isAuthenticated]) const handleLogin = async () => { await loginWithRedirect({ prompt: "login", appState: { returnTo: "/callback", }, }); } // ... 其他代码 })
Login函数
login = async () => { this.loading = true; try { console.log("Calling API to get currentUser"); var user = await agent.CurrentUserApi.get(); console.log("currentUser: ", user); } catch(error) { runInAction(() => this.loading = false); throw error; } }
有问题的Axios拦截器
axios.interceptors.request.use(config => { const { getAccessTokenSilently } = useAuth0(); config.headers = config.headers ?? {}; const token = getAccessTokenSilently(); if (token) { config.headers.Authorization = `Bearer ${token}` } return config; })
错误原因
React Hook必须遵循调用规则:只能在函数组件的顶层作用域或自定义Hook的顶层作用域中调用。你直接在Axios拦截器这个普通函数内部使用useAuth0() Hook,违反了这一规则,导致React无法正确追踪Hook的调用上下文,从而抛出错误。
另外,原拦截器中getAccessTokenSilently()是异步函数,直接赋值const token = getAccessTokenSilently()得到的是Promise对象,而非实际的token字符串,即使Hook调用合法,这里也会导致请求头设置错误。
解决方案
方案一:在顶层组件中初始化带Auth的Axios拦截器
在应用的顶层函数组件(如App.js)中,合法使用useAuth0 Hook,并在useEffect中创建Axios请求拦截器:
import { useAuth0 } from '@auth0/auth0-react'; import axios from 'axios'; import { useEffect } from 'react'; function App() { const { getAccessTokenSilently } = useAuth0(); useEffect(() => { // 创建拦截器并保存ID,用于卸载时清理 const requestInterceptorId = axios.interceptors.request.use(async (config) => { config.headers = config.headers ?? {}; try { // 异步获取token const token = await getAccessTokenSilently(); if (token) { config.headers.Authorization = `Bearer ${token}`; } } catch (err) { console.error('获取Auth0 token失败:', err); } return config; }); // 组件卸载时移除拦截器,避免内存泄漏 return () => { axios.interceptors.request.eject(requestInterceptorId); }; }, [getAccessTokenSilently]); // ... 渲染其他组件 }
方案二:封装自定义Hook创建带认证的Axios实例
如果需要更灵活的控制,可封装一个自定义Hook,返回已配置认证拦截器的Axios实例:
import { useAuth0 } from '@auth0/auth0-react'; import axios from 'axios'; import { useEffect, useRef } from 'react'; function useAxiosWithAuth() { const { getAccessTokenSilently } = useAuth0(); const axiosInstance = useRef(axios.create()).current; useEffect(() => { const interceptorId = axiosInstance.interceptors.request.use(async (config) => { config.headers = config.headers ?? {}; const token = await getAccessTokenSilently(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); return () => { axiosInstance.interceptors.request.eject(interceptorId); }; }, [getAccessTokenSilently]); return axiosInstance; } // 在组件中使用示例 function UserSettings() { const axiosWithAuth = useAxiosWithAuth(); const fetchUserSettings = async () => { try { const response = await axiosWithAuth.get('/api/current-user'); // 处理用户数据 } catch (err) { console.error('获取用户设置失败:', err); } }; return <button onClick={fetchUserSettings}>获取用户设置</button>; }
这两种方案都严格遵循了React Hook的调用规则,同时正确处理了getAccessTokenSilently()的异步逻辑,确保请求头能带上有效的access_token。
内容的提问来源于stack exchange,提问作者Lex
相关产品推荐
相关产品推荐

