React Router TS中父组件向子路由组件传递accessToken失败求助
问题:React Router中无法将accessToken传递给子路由组件
我是React和TypeScript新手,尝试在React Router中将accessToken从父组件传递给子路由组件。父组件中能打印看到该密钥,但无法传递给子组件,已尝试数小时未解决。
原代码问题分析
初始版本问题
- 使用全局变量
token存储accessToken,React组件不会监听全局变量变化,PowerBIClient挂载后不会因token更新重新渲染,始终拿到初始空值。 ProfileContent内部维护accessToken状态,与路由组件不在同一状态层级,无法直接共享。
更新版本问题
App组件给ProfileContent传参时,仅传递accessToken未传递setAccessToken,导致ProfileContent调用setAccessToken报错。useEffect依赖数组为空,未加入instance和accounts,存在闭包问题,可能无法获取最新MSAL实例和用户账户。setAccessToken后立刻打印accessToken,因setState是异步操作,此时打印的仍是旧值。
修正方案
- 状态提升与共享:将
accessToken状态提升到App组件,确保路由组件和ProfileContent共享同一状态源,状态更新时所有依赖组件自动重新渲染。 - 正确传递状态更新函数:给
ProfileContent传递setAccessToken,允许其更新App中的全局状态。 - 修复useEffect依赖:将
instance和accounts加入useEffect依赖数组,避免闭包导致的旧值引用。 - 处理异步状态更新:通过
useEffect监听accessToken变化实现打印,而非在setAccessToken后直接打印。 - TypeScript类型优化:给组件props定义明确类型,避免
any类型,提升代码健壮性。
修正后的完整代码
App组件
import { useState, useEffect } from 'react'; import { Routes, Route, Link, useResolvedPath, useMatch } from 'react-router-dom'; import { MsalProvider, useMsal, AuthenticatedTemplate, UnauthenticatedTemplate } from '@azure/msal-react'; import { IPublicClientApplication } from '@azure/msal-browser'; import Oversikt from './Oversikt'; import Mineoppdrag from './Mineoppdrag'; import PowerBIClient from './PowerBIClient'; import Ansvarsområde from './Ansvarsområde'; import ProfileData from './ProfileData'; import ProfilePic from './ProfilePic'; import PageLayout from './PageLayout'; function ActiveLink({ to, children, ...props }: { to: string; children: React.ReactNode; [key: string]: any }) { const resultPath = useResolvedPath(to); const isActive = useMatch({ path: resultPath.pathname, end: true }); return ( <li className={isActive ? "active" : ""}> <Link to={to} {...props}> {children} </Link> </li> ); } type AppProps = { pca: IPublicClientApplication }; type ProfileContentProps = { accessToken: string; setAccessToken: React.Dispatch<React.SetStateAction<string>>; }; function ProfileContent({ accessToken, setAccessToken }: ProfileContentProps) { const { instance, accounts } = useMsal(); const [graphData, setGraphData] = useState<any | null>(null); const name = accounts[0]?.name?.split(',')[1]; useEffect(() => { if (!accounts[0]) return; const request = { ...loginRequest, account: accounts[0] }; instance.acquireTokenSilent(request) .then((response) => { callMsGraph(response.accessToken) .then(setGraphData) .catch((e) => console.log(e)); if (response.accessToken) { setAccessToken(response.accessToken); } }) .catch((e) => { console.log(e, "TokenPopUPError"); instance.acquireTokenPopup(request) .then((response) => { callMsGraph(response.accessToken) .then(setGraphData); setAccessToken(response.accessToken); }); }); }, [instance, accounts, setAccessToken]); useEffect(() => { console.log(accessToken + "TOKEN"); }, [accessToken]); return ( <> <p className="card-title">Velkommen tilbake,</p> <h5 className="navBar-name">{name}</h5> <h5></h5> <Link to="/oversikt" className="test">Min oversikt</Link> <Link to="/oppdrag" className="test">Mine oppdrag</Link> <Link to="/ansvarsområde" className="test">Mitt ansvarsområde</Link> {graphData && accessToken ? ( <div> <ProfileData graphData={graphData} /> <ProfilePic accessToken={accessToken} /> </div> ) : ( <h5>Something bad happened, error!</h5> )} </> ); } function App({ pca }: AppProps) { const [accessToken, setAccessToken] = useState<string>(""); return ( <div> <nav> <MsalProvider instance={pca}> <Routes> <Route path="/oversikt" element={<Oversikt />} /> <Route path="/oppdrag/*" element={<Mineoppdrag />} /> <Route path="/oppdrag/rapport" element={<PowerBIClient accessToken={accessToken} />} /> <Route path="/ansvarsområde" element={<Ansvarsområde />} /> <Route path="/login" element={<UnauthenticatedTemplate />} /> </Routes> <PageLayout> <AuthenticatedTemplate> <div id="container"> <ProfileContent accessToken={accessToken} setAccessToken={setAccessToken} /> </div> </AuthenticatedTemplate> <UnauthenticatedTemplate> <div className="signinText">You are not signed in. Please sign in!</div> </UnauthenticatedTemplate> </PageLayout> </MsalProvider> </nav> </div> ); } export default App;
PowerBIClient组件
import React from 'react'; import { PowerBIEmbed } from 'powerbi-client-react'; import * as models from 'powerbi-models'; interface PowerBIClientProps { accessToken: string; } declare global { interface Window { report: any; } } export default class PowerBIClient extends React.Component<PowerBIClientProps> { public render() { console.log(this.props.accessToken + "TOKEN"); return ( <PowerBIEmbed embedConfig={{ type: 'report', embedUrl: "url", accessToken: this.props.accessToken, tokenType: models.TokenType.Aad, settings: { panes: { filters: { expanded: false, visible: true } }, } }} eventHandlers={new Map([ ['loaded', () => console.log('Report loaded')], ['rendered', () => console.log('Report rendered')], ['error', (event: any) => console.log(event.detail)] ])} cssClassName="Embed-container" getEmbeddedComponent={(embeddedReport) => { window.report = embeddedReport; console.log(this.props.accessToken + "TOKEN"); }} /> ); } }
关键说明
- 状态提升到App组件后,
accessToken的任何更新都会触发PowerBIClient重新渲染,确保拿到最新token。 useEffect依赖数组添加instance和accounts,保证用户账户或实例变化时重新获取token。- 明确的TypeScript类型定义避免了
any类型带来的潜在问题,提升代码可维护性。
内容的提问来源于stack exchange,提问作者Alexander Brandhaug
相关产品推荐
相关产品推荐

