React项目调用MS Graph API获取日历事件失败问题排查
问题原因
你抛出Invalid hook call错误的核心原因是:React Hook只能在函数组件的顶层作用域调用,不能在类组件的普通方法内部调用。
你的正常运行的importContact.jsx是函数组件,useMsal、useState都放在组件顶层,符合Hook调用规则;而出错的events.jsx是Class组件,你在类的getMicrosoftEvents方法内部调用了useMsal和useState,完全不符合Hook的使用要求,所以执行到这行直接报错中断,后续代码不会运行。
修复方案
方案1:将Events组件改为函数组件(推荐)
把Class组件重构为函数组件,用useEffect替代componentDidMount生命周期,所有Hook放在组件顶层调用,参考代码如下:
... import { loginRequest, graphConfig } from '../authConfig.jsx'; import { useMsal } from "@azure/msal-react"; import { useState, useEffect } from 'react'; const Events = (props) => { // Hook全部放在组件顶层调用 const { instance, accounts, inProgress } = useMsal(); const [accessToken, setAccessToken] = useState(null); const [graphData, setGraphData] = useState(null); useEffect(() => { var date = new Date(); var firstDay = new Date(date.getFullYear(), date.getMonth(), 1); var lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0); getMicrosoftEvents(firstDay, lastDay); }, []); // 空依赖数组等价于componentDidMount const getMicrosoftEvents = (start, end) => { console.log('log displayed in console') if(!accounts[0]) return; // 先判断是否有登录账号,避免报错 const request = { ...loginRequest, account: accounts[0] }; // 静默获取访问令牌后调用Graph接口 instance.acquireTokenSilent(request).then((response) => { console.log('acquireTokenSilent') setAccessToken(response.accessToken); callMsGraph(response.accessToken, start, end).then( response => { console.log('microsoft response ', response) setGraphData(response); } ); }).catch((e) => { console.log('microsoft response error ', e) instance.acquireTokenPopup(request).then((response) => { setAccessToken(response.accessToken); callMsGraph(response.accessToken, start, end).then( response => { console.log('microsoft response ', response) setGraphData(response); } ); }); }); } async function callMsGraph(accessToken, start, end) { console.log('callMsGraph ') const headers = new Headers(); const bearer = `Bearer ${accessToken}`; headers.append("Authorization", bearer); const options = { method: "GET", headers: headers }; // 注意:日期需要转成ISO格式,否则Graph API无法识别 return fetch(`${graphConfig.graphCalendarsEndpoint}?startDateTime=${start.toISOString()}&endDateTime=${end.toISOString()}`, options) .then(response => { // 这里要return response.json(),否则后续拿不到返回值 return response.json(); }) .catch(error => console.log(error)); } // 组件渲染逻辑... return <div>日历内容</div> } export default Events;
方案2:保留Class组件,使用MSAL提供的HOC包裹
如果你不想改Class组件的结构,可以使用@azure/msal-react提供的withMsal高阶组件来获取msal实例,不需要使用useMsal Hook,同时移除类组件内部的useState调用,改用类组件的this.state管理状态。
额外注意点
- 原代码中fetch的then回调里没有
return response.json(),会导致后续拿到的返回值为undefined,需要补上 - 传给Graph API的起止时间需要转成ISO 8601格式,否则接口会返回参数错误
- 调用接口前先判断
accounts[0]是否存在,避免用户未登录时调用报错
内容的提问来源于stack exchange,提问作者DouzeBri DouzeBra
相关产品推荐
相关产品推荐

