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

React widget仪表盘异步拉取API数据报Invalid hook call错误

报错根因

触发Invalid hook call. Hooks can only be called inside错误的核心原因是违反了React Hooks的基础调用规则:React Hooks(包括useState/useEffect/useCookies等)仅允许在React函数组件、自定义Hook的顶层作用域调用,禁止在普通工具函数、异步函数、循环分支、条件判断内部调用。
现有代码把useState、useEffect、useCookies全写在了普通异步工具函数WebserviceToken和ApiCall中,还在useEffect的循环逻辑里直接调用这些违规封装的函数,必然触发报错。

代码中存在的具体问题

  • 普通异步函数WebserviceToken既不是React组件,也不是自定义Hook,内部却调用了useState和useEffect,完全不符合Hooks调用规范。
  • 导出的请求工具函数ApiCall属于纯业务工具方法,内部违规调用useCookies、useState、useEffect,同样违反Hooks规则。
  • ApiCall和WebserviceToken里用useEffect包裹请求逻辑的写法完全无效:普通函数内的useEffect不会被React调度执行,状态更新、副作用逻辑都不会按预期运行,直接导致await ApiCall拿不到正确返回值,时序完全混乱。
  • Layout组件里遍历layouts拉取数据的逻辑存在竞态问题:异步请求还没返回就提前把null值的apiData存入Redux,后续请求完成后也不会更新对应widget的存储数据。

修正步骤

第一步:重构StatsService.js,移除所有违规Hooks调用

把文件改成纯异步工具函数,完全删除内部的useState/useEffect/useCookies调用。cookie操作直接用通用cookie读写工具(比如js-cookie库,或是自行封装原生document.cookie方法),请求逻辑直接用async/await实现,不需要套组件状态和副作用Hook。
修正后的代码参考:

import Cookies from 'js-cookie'

async function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function WebserviceToken() {
    const myHeaders = new Headers();
    myHeaders.append("Content-Type", "application/json");
    myHeaders.append("Origin", "*");
    
    const raw = JSON.stringify({
      "applicationToken": "--------",
      "customerUID": "--------",
      "privateKey": "--------"
    });

    const requestOptions = {
        method: 'POST',
        headers: myHeaders,
        body: raw,
        redirect: 'follow'
    };

    const response = await fetch(`https://somewebsite.com/webapi/auth/webservicekey`, requestOptions)
    if(response.status=== 200){
        return await response.json();
    }else if(response.status === 429){
        const RetryDelay = parseInt(response.headers.get("Retry-After")) * 1000;
        await timeout(RetryDelay);
        return await WebserviceToken();
    }else{
        console.log(response.status, response)
        throw new Error("获取接口鉴权token失败")
    }
}

export async function ApiCall({path,data}){
    let token = Cookies.get('auth2');
    // 本地无有效token时先请求token
    if(!token){
        const tokenRes = await WebserviceToken();
        token = tokenRes.token;
        Cookies.set('auth2', token, { path: '/' });
    }

    const myHeaders = new Headers();
    myHeaders.append("Origin", "*");
    myHeaders.append("Authorization", "Bearer "+token);

    let querystring ="";
    for (const [key, value] of Object.entries(data)) {
        querystring+="&"+key+"="+value;
    }
    if (querystring){
        querystring = "?"+querystring.substring(1);
    }

    const requestOptions = {
        method: 'GET',
        headers: myHeaders,
        redirect: 'follow'
    };

    const response = await fetch(`https://somewebsite.com/webapi/${path}${querystring}`, requestOptions)
    if(response.status === 200){
        return await response.json();
    }else if(response.status === 429){
        const RetryDelay = parseInt(response.headers.get("Retry-After")) * 1000;
        await timeout(RetryDelay);
        return await ApiCall({path, data});
    }else {
        console.log(response.status, response)
        throw new Error(`接口${path}请求失败`)
    }
}

第二步:修正Layout.js中的数据拉取逻辑

移除全局共享的apiData状态,每个widget的请求拿到结果后再单独更新Redux数据,避免竞态和数据覆盖。对应监听layouts的useEffect修正参考:

useEffect(() => {
  layouts.lg.forEach(async (item) =>{
    const existedData = widgetData.find(w =>w.id === item.i);
    if(existedData) return; // 已有缓存数据直接跳过

    const wJson = widgetJson.find(w=>w.id === item.i);
    if(!wJson?.liveData) return;

    try {
      const resData = await ApiCall({
        path:wJson.liveData.path,
        data:wJson.liveData.defaultValues
      });
      // 拿到接口返回后再更新Redux存储
      dispatch({ 
        type: 'set', 
        widgetData: [...widgetData, {id:item.id, data: resData}] 
      })
    } catch(err) {
      console.error(`widget ${item.i} 数据拉取失败`, err)
    }
  })
// 依赖项补充widgetData,避免重复发起已完成的请求
}, [layouts, widgetData, dispatch]);

开发注意事项

  • 所有React Hooks只能在两类场景调用:一是函数组件的顶层作用域,二是名称以use开头的自定义Hook顶层作用域,禁止在循环、条件判断、嵌套普通函数内调用Hooks。
  • 纯工具逻辑(接口请求、格式转换、存储读写等)不要和组件状态、Hook逻辑耦合,需要用到上下文数据时直接从组件层传参即可,不要在工具函数里违规调用Hooks。
  • 异步请求直接用async/await控制时序即可,不需要在工具函数里用useState/useEffect包裹,这类Hook只负责组件层面的状态管理和副作用调度。

内容的提问来源于stack exchange,提问作者Kasper Lauritsen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 18:24:41