fix(agent-office): useActivityFeed stale 응답 무시 (필터 변경 중 in-flight 요청)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,28 +12,35 @@ export function useActivityFeed(filters, refreshTrigger = 0) {
|
||||
|
||||
const offsetRef = useRef(0);
|
||||
const loadingRef = useRef(false);
|
||||
const requestIdRef = useRef(0);
|
||||
const filtersRef = useRef(filters);
|
||||
filtersRef.current = filters;
|
||||
|
||||
const filterKey = JSON.stringify(filters);
|
||||
|
||||
const fetchPage = useCallback(async (offset, replace) => {
|
||||
if (loadingRef.current) return;
|
||||
// append(loadMore)만 중복 방지. replace(필터/refresh 재조회)는 항상 우선 진행.
|
||||
if (!replace && loadingRef.current) return;
|
||||
const reqId = ++requestIdRef.current;
|
||||
loadingRef.current = true;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const data = await agentActivity({ ...filtersRef.current, limit: PAGE_SIZE, offset });
|
||||
if (reqId !== requestIdRef.current) return; // 더 새로운 요청이 시작됨 → stale 응답 무시
|
||||
const newItems = Array.isArray(data?.items) ? data.items : [];
|
||||
setTotal(data?.total || 0);
|
||||
setItems(prev => (replace ? newItems : [...prev, ...newItems]));
|
||||
offsetRef.current = offset + newItems.length;
|
||||
} catch (e) {
|
||||
if (reqId !== requestIdRef.current) return;
|
||||
setError(e.message || '불러오기 실패');
|
||||
} finally {
|
||||
if (reqId === requestIdRef.current) {
|
||||
loadingRef.current = false;
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -56,4 +56,18 @@ describe('useActivityFeed', () => {
|
||||
await waitFor(() => expect(result.current.items).toHaveLength(1));
|
||||
expect(result.current.hasMore).toBe(true);
|
||||
});
|
||||
|
||||
it('필터 변경 중이던 이전(stale) 요청 응답은 무시된다', async () => {
|
||||
let resolveFirst;
|
||||
const firstPromise = new Promise(r => { resolveFirst = r; });
|
||||
mockAgentActivity
|
||||
.mockReturnValueOnce(firstPromise) // 초기 요청 — 느리게 resolve
|
||||
.mockResolvedValueOnce({ items: [item({ task_id: 'fresh', agent_id: 'insta' })], total: 1 });
|
||||
const { result, rerender } = renderHook(({ f }) => useActivityFeed(f, 0), { initialProps: { f: { days: 7 } } });
|
||||
rerender({ f: { days: 7, agent_id: 'insta' } }); // 첫 요청 resolve 전에 필터 변경
|
||||
await waitFor(() => expect(result.current.items[0]?.task_id).toBe('fresh'));
|
||||
await act(async () => { resolveFirst({ items: [item({ task_id: 'stale' })], total: 99 }); });
|
||||
expect(result.current.items[0].task_id).toBe('fresh'); // stale이 덮어쓰지 않음
|
||||
expect(result.current.total).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user