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

Refine项目中更新组件状态后React组件无法正确渲染数据

Refine框架自定义数据获取后状态未更新问题排查与解决

在使用Refine构建仪表盘时,自定义fetch获取数据后,组件内的setting和stats状态始终停留在初始值(null),尽管在fetch回调里能打印出正确的数据,同时组件渲染时loading状态提前结束,导致后续依赖状态的UI无法正常渲染。

问题代码

import { Box, Card, Grid, Stack, Typography } from "@mui/material";
import { useApiUrl, useParsed } from "@refinedev/core";
import { Show } from "@refinedev/mui";
import { ICampaignStatistics, ISetting } from "../../interfaces";
import React, { useEffect, useState } from "react";

export const CampaignShow: React.FC = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [setting, setSetting] = useState<ISetting | null>(null);
  const [stats, setStats] = useState<ICampaignStatistics | null>(null);

  const apiUrl = useApiUrl();
  const { id } = useParsed();

  useEffect(() => {
    const headers = {
      "Access-Control-Allow-Origin": "*",
      Authorization: `Bearer ${localStorage.getItem("access_token")}`,
    };
    const controller = new AbortController();
    const signal = controller.signal;

    (async () => {
      try {
        const settingRes = await fetch(`${apiUrl}/setting`, {
          headers,
          signal,
        });
        const settingData = await settingRes.json();
        setSetting(settingData?.data as unknown as ISetting);

        // 这里能打印出正确的数据!
        console.log(statsData);

        const statsRes = await fetch(`${apiUrl}/campaigns/${id}/stats`, {
          headers,
          signal: controller.signal,
        });
        const statsData = await statsRes.json();
        setStats(statsData?.data as unknown as ICampaignStatistics);

        setIsLoading(false);
      } catch (e) {
        setIsLoading(false);
        console.log(e);
      }
    })();

    return () => controller.abort("");
  }, []);

  if (isLoading) {
    return <div>Loading...</div>;
  }

  // 这里总是打印null(初始状态)或undefined(应该是更新后的setting数据)
  console.log(setting);

  return (
    <Show>
      {stats && stats.whiteLocked}
      {setting && (
        <Grid container spacing={2}>
          // ... 省略部分代码 ...
        </Grid>
      )}
    </Show>
  );
};

问题分析

  1. 提前引用未定义变量:代码中console.log(statsData);在statsData声明前执行,直接抛出ReferenceError,导致异步流程进入catch分支,setIsLoading(false)执行但状态未正确赋值。
  2. useEffect依赖缺失:依赖数组为空,但内部用到apiUrl和id,若这两个值变化或初始未就绪(如id为undefined),会导致请求错误或无法重新触发数据获取。
  3. 未处理HTTP错误:fetch仅在网络故障时抛出错误,4xx/5xx状态码不会触发catch,此时返回的可能是错误信息而非预期数据,导致状态设置异常。
  4. 串行请求效率低下:当前串行请求setting和stats,增加了整体加载时间,且无必要。

解决方案

  1. 修复变量引用错误:移除提前打印statsData的代码,或移至变量定义后。
  2. 补充useEffect依赖:将apiUrl和id加入依赖数组,确保值变化时重新获取数据。
  3. 添加HTTP错误处理:检查响应的ok属性,非ok状态主动抛出错误。
  4. 并行请求优化:使用Promise.all同时发起请求,提升加载效率。

修改后的代码

import { Box, Card, Grid, Stack, Typography } from "@mui/material";
import { useApiUrl, useParsed } from "@refinedev/core";
import { Show } from "@refinedev/mui";
import { ICampaignStatistics, ISetting } from "../../interfaces";
import React, { useEffect, useState } from "react";

export const CampaignShow: React.FC = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [setting, setSetting] = useState<ISetting | null>(null);
  const [stats, setStats] = useState<ICampaignStatistics | null>(null);

  const apiUrl = useApiUrl();
  const { id } = useParsed();

  useEffect(() => {
    // 无id时不发起无效请求
    if (!id) {
      setIsLoading(false);
      return;
    }

    const headers = {
      Authorization: `Bearer ${localStorage.getItem("access_token")}`,
      // Access-Control-Allow-Origin是响应头,无需在请求中设置
    };
    const controller = new AbortController();
    const signal = controller.signal;

    (async () => {
      try {
        // 并行发起两个请求
        const [settingRes, statsRes] = await Promise.all([
          fetch(`${apiUrl}/setting`, { headers, signal }),
          fetch(`${apiUrl}/campaigns/${id}/stats`, { headers, signal }),
        ]);

        // 检查请求是否成功
        if (!settingRes.ok) throw new Error(`获取设置失败: ${settingRes.status}`);
        if (!statsRes.ok) throw new Error(`获取统计数据失败: ${statsRes.status}`);

        const settingData = await settingRes.json();
        const statsData = await statsRes.json();

        setSetting(settingData?.data as unknown as ISetting);
        setStats(statsData?.data as unknown as ICampaignStatistics);
      } catch (e) {
        console.error('数据获取失败:', e);
      } finally {
        // 无论成功失败都结束loading
        setIsLoading(false);
      }
    })();

    return () => controller.abort("组件卸载,终止请求");
  }, [apiUrl, id]); // 添加必要依赖

  if (isLoading) {
    return <div>Loading...</div>;
  }

  console.log('当前setting状态:', setting);

  return (
    <Show>
      {stats && <Typography>{stats.whiteLocked}</Typography>}
      {setting && (
        <Grid container spacing={2}>
          // ... 省略部分代码 ...
        </Grid>
      )}
    </Show>
  );
};

额外注意点

  • 移除了无效的请求头Access-Control-Allow-Origin,该字段是服务器返回的响应头,无需在请求中设置。
  • 增加了id的前置判断,避免发起/campaigns/undefined/stats这类无效请求。
  • 使用finally块确保loading状态一定会被更新,避免出现无限loading的情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 20:43:13