From 0ec1c6a5501d9ec6bfb3e78baae3130d13b0513f Mon Sep 17 00:00:00 2001 From: PorunC <09982.misaka@gmail.com> Date: Wed, 29 Oct 2025 20:49:22 +0800 Subject: [PATCH] =?UTF-8?q?Fix:=20Resolve=20React=20Hook=20violation=20in?= =?UTF-8?q?=20ComparisonChart=20component=20Root=20cause:=20ComparisonChar?= =?UTF-8?q?t=20was=20calling=20useSWR=20hooks=20dynamically=20inside=20a?= =?UTF-8?q?=20.map()=20loop,=20which=20violates=20React's=20Rules=20of=20H?= =?UTF-8?q?ooks.=20When=20the=20traders=20array=20length=20changed=20(e.g.?= =?UTF-8?q?,=20from=200=20to=202,=20or=20during=20initial=20load),=20the?= =?UTF-8?q?=20number=20of=20hook=20calls=20would=20change=20between=20rend?= =?UTF-8?q?ers,=20triggering=20React=20Error=20#310.=20Previous=20code:=20?= =?UTF-8?q?```tsx=20const=20traderHistories=20=3D=20traders.map((trader)?= =?UTF-8?q?=20=3D>=20{=20=20=20return=20useSWR(`equity-history-${trader.tr?= =?UTF-8?q?ader=5Fid}`,=20...);=20=20//=20=E2=9D=8C=20Dynamic=20hooks=20})?= =?UTF-8?q?;=20```=20The=20eslint-disable=20comment=20on=20line=2024=20was?= =?UTF-8?q?=20masking=20this=20critical=20issue.=20Fix:=20-=20Always=20cal?= =?UTF-8?q?l=20exactly=202=20useSWR=20hooks=20(trader1,=20trader2)=20uncon?= =?UTF-8?q?ditionally=20-=20Pass=20null=20as=20the=20key=20when=20trader?= =?UTF-8?q?=20doesn't=20exist=20(SWR=20handles=20this=20gracefully)=20-=20?= =?UTF-8?q?Build=20traderHistories=20array=20from=20these=20fixed=20hooks?= =?UTF-8?q?=20-=20Ensures=20same=20number=20of=20hooks=20called=20on=20eve?= =?UTF-8?q?ry=20render=20This=20follows=20React's=20Rules=20of=20Hooks:=20?= =?UTF-8?q?=E2=9C=85=20Only=20call=20hooks=20at=20the=20top=20level=20?= =?UTF-8?q?=E2=9C=85=20Don't=20call=20hooks=20inside=20loops,=20conditions?= =?UTF-8?q?,=20or=20nested=20functions=20=E2=9C=85=20Call=20hooks=20in=20t?= =?UTF-8?q?he=20same=20order=20every=20render=20Fixes:=20React=20Error=20#?= =?UTF-8?q?310=20(Rendered=20more=20hooks=20than=20during=20previous=20ren?= =?UTF-8?q?der)=20Co-Authored-By:=20tinkle-community=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/ComparisonChart.tsx | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/web/src/components/ComparisonChart.tsx b/web/src/components/ComparisonChart.tsx index ee94ef00..3c0b5999 100644 --- a/web/src/components/ComparisonChart.tsx +++ b/web/src/components/ComparisonChart.tsx @@ -19,14 +19,24 @@ interface ComparisonChartProps { } export function ComparisonChart({ traders }: ComparisonChartProps) { - // 获取所有trader的历史数据 - const traderHistories = traders.map((trader) => { - // eslint-disable-next-line react-hooks/rules-of-hooks - return useSWR(`equity-history-${trader.trader_id}`, () => - api.getEquityHistory(trader.trader_id), - { refreshInterval: 10000 } - ); - }); + // 获取所有trader的历史数据 - 修复: 使用固定数量的Hook调用 + // 始终调用最多2个trader的useSWR,即使实际trader数量不同 + const trader1 = traders[0]; + const trader2 = traders[1]; + + const history1 = useSWR( + trader1 ? `equity-history-${trader1.trader_id}` : null, + trader1 ? () => api.getEquityHistory(trader1.trader_id) : null, + { refreshInterval: 10000 } + ); + + const history2 = useSWR( + trader2 ? `equity-history-${trader2.trader_id}` : null, + trader2 ? () => api.getEquityHistory(trader2.trader_id) : null, + { refreshInterval: 10000 } + ); + + const traderHistories = [history1, history2].slice(0, traders.length); // 使用useMemo自动处理数据合并,直接使用data对象作为依赖 const combinedData = useMemo(() => {