⏳ React에서 컴포넌트 언마운트와 비동기 오류 대응하기
React로 비동기 API 요청을 처리하다 보면 간헐적인 에러 페이지 이동이나 불필요한 상태 업데이트 등 예상치 못한 문제가 발생할 수 있다.
이 글에서는 실제로 겪었던 이슈를 바탕으로 아래 세 가지를 정리했다.
- useEffect 내부 비동기 처리 시 발생할 수 있는 문제
- 언마운트된 컴포넌트에서의 상태 업데이트 방지 방법
- 더 나은 데이터 패칭 대안
❗ 문제 상황
게시글 리스트와 디테일 페이지를 구현한 후, API 요청 실패 시 에러 페이지(/error)로 이동하도록 예외 처리를 했다.
하지만 테스트 중 간헐적으로 잘못된 에러 페이지 이동 현상이 발생했다.
useEffect(() => {
fetchData()
.then((res) => {
setData(res);
})
.catch((err) => navigate('/error'));
}, []);
이 경우, fetchData()가 완료되기 전에 컴포넌트가 언마운트되면,
이후 setState() 또는 navigate() 호출로 인해 React 경고가 발생하거나 잘못된 화면 전환이 일어난다.
🔍 원인 분석
- 사용자가 빠르게 페이지를 이동하거나 테스트 자동화 도구가 다음 페이지로 전환할 경우
- 응답이 돌아온 시점엔 컴포넌트가 이미 언마운트 되어있음
- 그럼에도 불구하고 setState()나 navigate()가 호출되며 에러 발생
React 공식 문서에 따르면, 이런 상황은 useEffect의 cleanup 함수로 방지할 수 있다.
🔗 React 공식 문서: Reusing Logic with Custom Hooks
Reusing Logic with Custom Hooks – React
The library for web and native user interfaces
react.dev
✅ 해결 방법: 언마운트 체크
isMounted 플래그를 통해 컴포넌트가 여전히 마운트 상태인지 확인하고 상태 업데이트를 조건부로 수행한다.
useEffect(() => {
let isMounted = true;
fetchData(id)
.then((res) => {
if (isMounted) setContest(res);
})
.catch((err) => {
if (!isMounted) return;
navigate("/error", { state: { errorMessage: "불러오기 실패" } });
});
return () => {
isMounted = false;
};
}, [id, navigate]);
이 방식은 컴포넌트가 언마운트되었을 때, 불필요한 상태 업데이트나 잘못된 페이지 이동을 방지할 수 있으며
useEffect의 cleanup 함수는 단순히 타이머나 리스너 해제를 넘어, 비동기 안정성을 보장한다.
❗️ useEffect에서 직접 fetch의 문제점
하지만 리액트는 useEffect를 사용한 데이터 페칭을 권장하고 있지 않다.
물론, 이 방식도 일종의 보일러플레이트이기 때문에 아래처럼 매번 ignore 플래그를 써야 하는 것도 귀찮고 실수 유발 가능성도 크다.
useEffect(() => {
let ignore = false;
fetch(...)
.then(result => {
if (!ignore) setData(result);
});
return () => {
ignore = true;
};
}, [dep]);
- SSR 불가 : useEffect는 클라이언트에서만 실행되므로, 서버 사이드 렌더링이 불가능
- 느린 로딩 : 부모 -> 자식 순서로 요청되면서 네트워크 병목 발생 (waterfall)
- 캐싱 없음 : 언마운트 후 다시 마운트되면 매번 재요청
- 중목 로직 : 재사용성이 떨어져 여러 컴포넌트에서 중복 발생
- 레이스 컨디션 방어 필요 : 매번 ignore로 직접 방어 필요
✅ 더 나은 대안: 데이터 패칭 도구 사용
그래서 리액트는 복잡한 비동기 흐름을 직접 관리하기보다는 다음과 같은 라이브러리나 프레임워크 기능으로 추상화하는 걸 권장한다.
- TanStack Query (React Query) : 자동 캐싱, 중복 방지, 상태 관리, 레이스 컨디션 방지
- SWR : Next.js와 궁합이 좋음, 캐싱 및 재검증 가능
- React Router v6.4+ : loader 기반의 데이터 페칭 구조 지원
이런 도구들은 아래와 같은 기능을 자동으로 처리해 준다.
- 자동 캐싱 및 요청 중복 방지
- 상태 동기화 및 재시도
- race condition 자동 방어
- 서버/클라이언트 간 일관된 데이터 흐름
📝 마무리
비동기 로직을 직접 다루는 방식은 간단해 보이지만, 컴포넌트 언마운트나 레이스 컨디션 등에서 예기치 않은 문제가 발생할 수 있다.
useEffect와 isMounted 패턴은 이런 상황에 대한 기본적인 방어는 가능하지만, 반복적이고 실수 여지가 많은 방식이다.
보다 안정적인 데이터 패칭과 유지보수를 위해서는 TanStack Query 같은 라이브러리 도입을 고려하는 것이 장기적으로 효율적인 선택이다.
'개발일지 > React' 카테고리의 다른 글
[SkeletonUI] 데이터 로딩중 스켈레톤 UI 적용하여 사용자 경험 향상시키기 (2) | 2025.03.12 |
---|