Next.js
React 탭 전환에서 useEffect가 예전 데이터를 덮어쓴 실수 정리
빠른 탭 전환 중 이전 요청이 늦게 도착해 화면을 덮어쓴 문제를 고친 기록.
빠르게 클릭할 때만 틀렸다
통계 화면의 일·주·월 탭은 천천히 누르면 정상이었다. 하지만 일 탭 뒤 바로 월 탭을 누르면 늦게 온 일 요청이 월 화면을 덮었다. 의존성 배열에 range를 넣어도 이미 시작한 비동기 요청은 멈추지 않는다는 점을 놓쳤다.
cleanup에서 이전 요청 취소
effect 실행마다 컨트롤러를 새로 만들고 cleanup에서 취소했다. ref 하나를 공유하면 새 요청까지 취소할 수 있어 이 위치가 중요했다.
useEffect(() => {
const controller = new AbortController();
async function load() {
setLoading(true);
try {
const res = await fetch(`/api/stats?range=${range}`, { signal: controller.signal });
if (!res.ok) throw new Error('failed');
setStats(await res.json());
} catch (e) {
if ((e as Error).name !== 'AbortError') setError('다시 시도해 주세요.');
} finally {
if (!controller.signal.aborted) setLoading(false);
}
}
load();
return () => controller.abort();
}, [range]);
처음에는 finally에서 무조건 로딩을 껐다. 취소된 이전 요청이 새 요청의 로딩 표시를 꺼 버려서 aborted 조건을 추가했다.
재현과 마무리
개발자 도구에서 Slow 3G를 고르고 탭을 연속 클릭했다. 정상 네트워크에서는 숨은 문제가 바로 보였다.
- 응답 도착 순서는 보장되지 않는다.
- AbortError는 사용자 오류로 표시하지 않는다.
- 느린 네트워크에서도 빠르게 전환해 본다.
탭이 단순해 보여도 요청이 겹치면 경쟁 상태가 된다.