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> ); };
问题分析
- 提前引用未定义变量:代码中
console.log(statsData);在statsData声明前执行,直接抛出ReferenceError,导致异步流程进入catch分支,setIsLoading(false)执行但状态未正确赋值。 - useEffect依赖缺失:依赖数组为空,但内部用到
apiUrl和id,若这两个值变化或初始未就绪(如id为undefined),会导致请求错误或无法重新触发数据获取。 - 未处理HTTP错误:fetch仅在网络故障时抛出错误,4xx/5xx状态码不会触发catch,此时返回的可能是错误信息而非预期数据,导致状态设置异常。
- 串行请求效率低下:当前串行请求
setting和stats,增加了整体加载时间,且无必要。
解决方案
- 修复变量引用错误:移除提前打印
statsData的代码,或移至变量定义后。 - 补充useEffect依赖:将
apiUrl和id加入依赖数组,确保值变化时重新获取数据。 - 添加HTTP错误处理:检查响应的
ok属性,非ok状态主动抛出错误。 - 并行请求优化:使用
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
相关产品推荐
相关产品推荐

