使用@auth0/auth0-react的getAccessTokenSilently在useEffect触发无限循环求助
React Router 6 + Auth0 无限循环问题排查
环境依赖
react-router-dom 6.3.0 react 18.2 auth0-react 2.0.1
问题描述
遵循《React Router 6 实现 React 认证示例》教程开发,此前所有功能正常,但在useEffect()中调用getAccessTokenSilently()时触发了无限循环。直接调用该方法访问自有API无问题,仅在useEffect中调用时出现循环。
可复现代码片段
Auth0ProviderWithNavigate 实现
import { Auth0Provider } from '@auth0/auth0-react'; import React from 'react'; import { useNavigate } from 'react-router-dom'; export const Auth0ProviderWithNavigate = ({ children }) => { const navigate = useNavigate(); const onRedirectCallback = (appState) => { navigate(appState?.returnTo || window.location.pathname); }; if (!(domain && clientId && redirectUri)) { return null; } return ( <Auth0Provider domain={domain} clientId={clientId} authorizationParams={{ audience: audience, redirect_uri: redirectUri }} onRedirectCallback={onRedirectCallback} useRefreshTokens={true} > {children} </Auth0Provider> ); };
AuthenticationGuard 路由守卫
import { withAuthenticationRequired } from '@auth0/auth0-react'; export const AuthenticationGuard = ({ component }) => { const Component = withAuthenticationRequired(component, { onRedirecting: () => ( <div className="page-layout"> Loading... </div> ), }); return <Component />; };
index.tsx 入口文件
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { BrowserRouter } from 'react-router-dom'; import { Auth0ProviderWithNavigate } from './Auth0Provider'; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( <React.Fragment> <BrowserRouter> <Auth0ProviderWithNavigate> <App /> </Auth0ProviderWithNavigate> </BrowserRouter> </React.Fragment> ); reportWebVitals();
App.tsx 组件(出现无限打印)
import * as React from 'react'; import { Routes, Route } from 'react-router-dom'; import Home from 'pages/Home'; import { AuthenticationGuard } from 'components/AuthenticationGuard'; import { AppProvider } from './context'; import { useAuth0 } from '@auth0/auth0-react'; export default function App(): JSX.Element { const { isAuthenticated } = useAuth0(); console.log('isAuthenticated: ', isAuthenticated); // 无限循环打印 return ( <React.Fragment> <AppProvider> <div> <Routes> <Route path="/" element={<AuthenticationGuard component={Home} />} /> </Routes> </div> </AppProvider> </React.Fragment> ); }
AppProvider 上下文
import React, { createContext, useReducer } from 'react'; interface ProviderProps { children: React.ReactNode; } const AppContext = createContext<any>({}); const combinedReducers = (state: any, action: any) => { // 合并的reducer逻辑 return state; }; const initialState = {}; const AppProvider = ({ children }: ProviderProps) => { const [state, dispatch] = useReducer(combinedReducers, initialState); return <AppContext.Provider value={{ state, dispatch }}>{children}</AppContext.Provider>; }; export { AppProvider };
Home 组件(触发循环的核心)
import * as React from 'react'; import { useEffect } from 'react'; import { useAuth0 } from '@auth0/auth0-react'; export default function Home(): JSX.Element { const { getAccessTokenSilently } = useAuth0(); useEffect(() => { (async () => { const token = await getAccessTokenSilently({ authorizationParams: { audience: audience }, }); console.log('token ', token); // 无限循环打印 })(); }, []); return ( <div>Hello from app!</div> ); }
问题原因分析
- Auth0状态触发重渲染循环:
getAccessTokenSilently调用时,可能触发Auth0内部状态更新(比如token刷新、isAuthenticated状态变更)。App组件直接订阅了isAuthenticated,状态变化会导致App重渲染,进而重新渲染AuthenticationGuard和Home组件,Home的useEffect再次调用getAccessTokenSilently,形成闭环。 - useEffect依赖缺失:Home组件的useEffect依赖数组为空,但
getAccessTokenSilently或audience的引用可能在每次渲染时变化,导致React认为依赖更新,重复执行useEffect。 - Refresh Token配置影响:开启
useRefreshTokens={true}后,静默获取token时可能触发token刷新流程,多次更新Auth0内部状态,引发重复重渲染。
解决方向
- 优化App组件渲染:移除不必要的
isAuthenticated订阅(如仅用于调试的打印),或用React.memo包裹App组件,避免无意义的重渲染。 - 完善useEffect依赖:将
getAccessTokenSilently和audience加入useEffect的依赖数组,并用useCallback包裹异步函数确保引用稳定:
import { useAuth0 } from '@auth0/auth0-react'; import * as React from 'react'; import { useEffect, useCallback } from 'react'; export default function Home(): JSX.Element { const { getAccessTokenSilently } = useAuth0(); const fetchToken = useCallback(async () => { const token = await getAccessTokenSilently({ authorizationParams: { audience: audience }, }); console.log('token ', token); }, [getAccessTokenSilently, audience]); useEffect(() => { fetchToken(); }, [fetchToken]); return ( <div>Hello from app!</div> ); }
- 优化路由守卫组件:用
React.memo包裹被withAuthenticationRequired包装后的组件,避免认证状态变化时的不必要重渲染:
import { withAuthenticationRequired } from '@auth0/auth0-react'; import React from 'react'; export const AuthenticationGuard = ({ component }) => { const Component = React.memo(withAuthenticationRequired(component, { onRedirecting: () => ( <div className="page-layout"> Loading... </div> ), })); return <Component />; };
- 确保Auth0配置参数稳定:确认
domain、clientId、audience等参数是常量,而非每次渲染都生成的新值(比如避免从动态状态中获取,或用useMemo缓存),防止Auth0Provider反复初始化。
内容的提问来源于stack exchange,提问作者dgeorg
相关产品推荐
相关产品推荐

