React 18 업그레이드 후 useEffect 두 번 실행 이슈

문제 상황

프로젝트를 React 18로 업그레이드한 후, 개발 환경에서 useEffect 내부의 API 호출이 두 번씩 발생하는 것을 확인했다. 로그를 찍어보니 마운트/언마운트/재마운트 패턴으로 실행되고 있었다.

useEffect(() => {
  console.log('mount');
  fetchUserData();
  
  return () => console.log('unmount');
}, []);

// 출력:
// mount
// unmount
// mount

원인

React 18의 Strict Mode가 강화되면서, 컴포넌트의 재사용성을 테스트하기 위해 의도적으로 마운트/언마운트를 시뮬레이션하도록 변경되었다. 이는 향후 추가될 기능(컴포넌트 상태 보존)을 대비한 것으로, 개발 환경에서만 발생한다.

해결 방법

1. cleanup 함수 제대로 구현

useEffect(() => {
  const controller = new AbortController();
  
  fetchUserData({ signal: controller.signal })
    .then(data => setUser(data))
    .catch(err => {
      if (err.name !== 'AbortError') {
        console.error(err);
      }
    });
  
  return () => controller.abort();
}, []);

2. ref로 중복 실행 방지 (비추천)

const fetched = useRef(false);

useEffect(() => {
  if (fetched.current) return;
  fetched.current = true;
  
  fetchUserData();
}, []);

이 방식은 Strict Mode의 의도를 우회하는 것이라 권장되지 않는다. cleanup 함수를 제대로 구현하는 것이 올바른 접근이다.

결론

처음엔 버그인 줄 알았지만, React 팀이 의도한 동작이었다. 덕분에 기존 코드에서 cleanup을 제대로 안 하던 부분들을 찾아낼 수 있었다. 프로덕션에서는 발생하지 않으니 당황하지 말고, 이 기회에 side effect 처리를 점검하면 좋겠다.