You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

动态遍历useSWR返回数据调用useSWR触发React Hook顺序错误求助

错误原因及解决方案

错误原因

你碰到的这个错误,核心是违反了React Hook的调用规则:

  • React要求所有Hook必须在组件/自定义Hook的顶层作用域调用,绝对不能放在循环、条件判断、嵌套函数(比如map的回调函数)里。
  • 原来的代码中,useSWR是在GoogleCalendarListResult?.items?.map()的回调里调用的:第一次渲染时GoogleCalendarListResult还没异步返回(值为undefined),不会执行这个map,也就不会调用对应的useSWR;当日历列表数据加载完成后,组件重新渲染,此时会执行map,新增了多个useSWR调用,导致两次渲染的Hook调用顺序、数量不一致,React因此抛出了这个错误。
  • 改成固定数组['calendarid1', 'calendarid2']后,每次渲染map的次数固定,Hook调用的顺序和数量完全一致,所以不会触发错误,但这种写法无法动态适配用户的实际日历列表。

可行解决方案

方案1:基于SWR批量请求所有日历事件

等日历列表加载完成后,在Hook顶层批量请求所有日历的事件,确保Hook调用顺序稳定:

import { Schedule, User } from '../../types/commons.type';
import { useLocalStorage } from 'usehooks-ts';
import useSWR from 'swr';
import { FetchCalendarList } from '../../types/googleCalendarAPIResponse.type';
import { currentUTCMonthString } from '../../utils/DateUtils';

const baseURL = 'https://www.googleapis.com/calendar/v3';

const fetcher = ([path, method, body = {}, accessToken = '']: [string, string, object, string]) => {
  const headers = {
    'Content-Type': ['POST', 'PUT', 'PATCH'].includes(method) ? 'application/json' : '',
    Authorization: `Bearer ${accessToken}`,
  };
  const fetchOptions = {
    method,
    headers,
    body: method === 'GET' ? undefined : JSON.stringify(body),
  };
  const apiURL = `${baseURL}${path}`;
  return fetch(apiURL, fetchOptions).then((r) => r.json());
};

export const useGoogleCalendarEvents = () => {
  const [user] = useLocalStorage<User | undefined>('user', undefined);
  const { startString, endString } = currentUTCMonthString();
  const parameters = `timeMin=${startString}&timeMax=${endString}`;

  // 第一步:获取用户日历列表
  const { data: calendarList } = useSWR<FetchCalendarList>(
    user?.googleAccessToken ? ['/users/me/calendarList', 'GET', {}, user.googleAccessToken] : null,
    fetcher,
  );

  // 第二步:批量请求所有日历的事件(仅当日历列表存在时)
  const calendarIds = calendarList?.items?.map(cal => cal.id) || [];
  const { data: allEventResults } = useSWR(
    calendarIds.length && user?.googleAccessToken 
      ? calendarIds.map(id => [`/calendars/${id}/events?${parameters}`, 'GET', {}, user.googleAccessToken])
      : null,
    (requestPaths) => Promise.all(requestPaths.map(path => fetcher(path))),
  );

  // 整理所有日历的事件到一个数组
  const events = allEventResults?.flatMap(result => result.items || []) || [];

  return { events, calendarList };
};

方案2:封装子组件处理单个日历事件

把单个日历的事件请求逻辑封装成独立子组件,父组件拿到日历列表后循环渲染子组件,这样每个子组件内部的useSWR都处于顶层作用域,符合规则:

// 子组件:负责单个日历的事件请求
const CalendarEventFetcher = ({ 
  calendarId, 
  accessToken, 
  parameters,
  onEventsLoaded
}: {
  calendarId: string;
  accessToken: string;
  parameters: string;
  onEventsLoaded: (events: any[]) => void;
}) => {
  const { data: eventResult } = useSWR(
    [`/calendars/${calendarId}/events?${parameters}`, 'GET', {}, accessToken],
    fetcher,
  );

  useEffect(() => {
    if (eventResult?.items) {
      onEventsLoaded(eventResult.items);
    }
  }, [eventResult, onEventsLoaded]);

  return null; // 仅做数据获取,无UI输出
};

// 原自定义Hook
export const useGoogleCalendarEvents = () => {
  const [user] = useLocalStorage<User | undefined>('user', undefined);
  const { startString, endString } = currentUTCMonthString();
  const parameters = `timeMin=${startString}&timeMax=${endString}`;
  const [allEvents, setAllEvents] = useState<any[]>([]);

  // 获取日历列表
  const { data: calendarList } = useSWR<FetchCalendarList>(
    user?.googleAccessToken ? ['/users/me/calendarList', 'GET', {}, user.googleAccessToken] : null,
    fetcher,
  );

  // 收集所有子组件返回的事件
  const handleEventsLoaded = (events: any[]) => {
    setAllEvents(prev => [...prev, ...events]);
  };

  // 注意:该方案需要在使用Hook的组件中渲染CalendarEventFetcher
  // 若要纯数据Hook,方案1更合适

  return { 
    events: allEvents, 
    calendarList,
    CalendarEventFetcher, // 暴露子组件供外部渲染
    parameters,
    userAccessToken: user?.googleAccessToken
  };
};

内容的提问来源于stack exchange,提问作者Eddie Ciou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 23:15:35