Skip to content

Commit d48ee35

Browse files
sudoghutclaude
andauthored
修復 TabContentLoader 切換人物後永久顯示「載入中…」 (#981)
#980 合併後的回歸修復: - 移除 effect 對 cache 引用的依賴,避免 fetch 完成 → cache 變化 → effect cleanup abort → 資料永遠無法到達的惡性循環 - 新增 fetchSeq counter 驅動 retry / save 後的重新載入 - effect 內用 setCache updater 讀取最新 cache,避免依賴引用 - 切人時用 personChanged 布林值確保本次渲染直接使用空快取 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d52cbf5 commit d48ee35

1 file changed

Lines changed: 26 additions & 22 deletions

File tree

resources/js/inertia/components/PersonBrowser/TabContentLoader.tsx

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { useEffect, useRef, useState } from 'react';
22
import BasicInfoView from './BasicInfoView';
33
import AltNamesTab from './tabs/AltNamesTab';
44
import AddressesTab from './tabs/AddressesTab';
@@ -68,36 +68,40 @@ export default function TabContentLoader({
6868
onBasicInfoEditorStateChange,
6969
onRegisterBasicInfoSaveHandler,
7070
}: Props) {
71-
const [cacheState, setCacheState] = useState<{ personId: number | null; tabs: Record<string, TabState> }>({
72-
personId,
73-
tabs: {},
74-
});
71+
const [cache, setCache] = useState<Record<string, TabState>>({});
72+
const [fetchSeq, setFetchSeq] = useState(0);
73+
const cachePersonRef = useRef<number | null>(personId);
7574

76-
// 人物切換時重設快取;本次渲染直接使用空快取,不等下一輪
77-
const personChanged = cacheState.personId !== personId;
75+
// 人物切換時同步清快取,本次渲染直接使用空值
76+
const personChanged = cachePersonRef.current !== personId;
7877
if (personChanged) {
79-
setCacheState({ personId, tabs: {} });
78+
cachePersonRef.current = personId;
79+
if (Object.keys(cache).length > 0) {
80+
setCache({});
81+
}
8082
}
8183

82-
const cache = personChanged ? {} : cacheState.tabs;
83-
const setCache = (updater: Record<string, TabState> | ((prev: Record<string, TabState>) => Record<string, TabState>)) => {
84-
setCacheState((prev) => ({
85-
...prev,
86-
tabs: typeof updater === 'function' ? updater(prev.tabs) : updater,
87-
}));
88-
};
84+
const effectiveCache = personChanged ? {} : cache;
8985

90-
// lazy load
86+
// lazy load — 由 personId / activeTab / fetchSeq 驅動
9187
useEffect(() => {
9288
if (personId == null || !activeTab) return;
93-
if (cache[activeTab]) return;
89+
90+
// 透過 updater 讀取最新 cache,避免把 cache 引用放進依賴
91+
let alreadyCached = false;
92+
setCache((prev) => {
93+
if (prev[activeTab]) {
94+
alreadyCached = true;
95+
return prev;
96+
}
97+
return { ...prev, [activeTab]: { loading: true, error: null, data: null } };
98+
});
99+
if (alreadyCached) return;
94100

95101
const url = tabEndpoint
96102
.replace('__PERSON_ID__', String(personId))
97103
.replace('__TAB_KEY__', activeTab);
98104

99-
setCache((prev) => ({ ...prev, [activeTab]: { loading: true, error: null, data: null } }));
100-
101105
const controller = new AbortController();
102106

103107
fetch(url, { signal: controller.signal })
@@ -124,22 +128,22 @@ export default function TabContentLoader({
124128
return () => {
125129
controller.abort();
126130
};
127-
}, [personId, activeTab, tabEndpoint, cache]);
131+
}, [personId, activeTab, tabEndpoint, fetchSeq]);
128132

129133
const retryActiveTab = () => {
130134
setCache((prev) => {
131135
const next = { ...prev };
132136
delete next[activeTab];
133-
134137
return next;
135138
});
139+
setFetchSeq((s) => s + 1);
136140
};
137141

138142
if (personId == null) {
139143
return null;
140144
}
141145

142-
const state = cache[activeTab];
146+
const state = effectiveCache[activeTab];
143147

144148
if (!state || state.loading) {
145149
return <div style={msgStyle}>載入中…</div>;

0 commit comments

Comments
 (0)