개발일지/React

[useEffect] 컴포넌트 언마운트와 비동기 오류 대응

ayeongjin 2025. 4. 21. 15:16

⏳ React에서 컴포넌트 언마운트와 비동기 오류 대응하기

React로 비동기 API 요청을 처리하다 보면 간헐적인 에러 페이지 이동이나 불필요한 상태 업데이트 등 예상치 못한 문제가 발생할 수 있다.
이 글에서는 실제로 겪었던 이슈를 바탕으로 아래 세 가지를 정리했다.

  1. useEffect 내부 비동기 처리 시 발생할 수 있는 문제
  2. 언마운트된 컴포넌트에서의 상태 업데이트 방지 방법
  3. 더 나은 데이터 패칭 대안

❗ 문제 상황

게시글 리스트와 디테일 페이지를 구현한 후, 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 같은 라이브러리 도입을 고려하는 것이 장기적으로 효율적인 선택이다.