← 전체 글로 돌아가기

Next.js

React useEffect가 멈추지 않을 때 원인 찾는 법

useEffect가 예상보다 많이 실행된다면 의존성 배열이나 effect 안에서의 상태 변경이 원인인 경우가 대부분이다.

useEffect가 계속 실행된다는 건 effect가 촉발하는 렌더링이 다시 effect를 실행시키는 루프가 생겼다는 뜻이다. 원인 후보는 몇 가지로 좁혀진다.

의존성 배열에 객체나 배열이 들어간 경우

useEffect(() => {
  fetchData();
}, [options]); // options가 매 렌더마다 새로 만들어지면 무한루프

객체나 배열을 deps에 넣으면 참조 비교를 하기 때문에 내용이 같아도 매 렌더에서 새로 만들어진 값은 다른 것으로 판단한다. useMemo로 참조를 안정화하거나, 의존성을 원시값(string, number)으로 바꿔야 한다.

effect 안에서 deps에 있는 상태를 변경하는 패턴

useEffect(() => {
  setCount(count + 1); // count가 deps에 있으면 무한루프
}, [count]);

effect 안에서 의존성 배열에 포함된 상태를 변경하면 루프가 생긴다. 함수형 업데이트(setCount(prev => prev + 1))를 쓰고 count를 deps에서 제거하면 해결된다.

콘솔로 렌더링 추적

어느 타이밍에 effect가 실행되는지 먼저 파악한다.

useEffect(() => {
  console.log('effect ran', { someValue });
}, [someValue]);

React DevTools Profiler에서 어떤 컴포넌트가 얼마나 자주 렌더됐는지도 볼 수 있다. 비정상적으로 많이 렌더되는 컴포넌트가 있으면 거기서 시작해서 올라간다.

이벤트 리스너 정리 누락

useEffect(() => {
  window.addEventListener('resize', handler);
  return () => window.removeEventListener('resize', handler); // 정리 필수
}, []);

클린업 함수 없이 이벤트 리스너를 등록하면 컴포넌트가 마운트될 때마다 리스너가 쌓인다. 모바일에서 키보드가 올라오면서 resize가 반복되는 패턴과 만나면 무한 루프처럼 보인다.

빌드 경고 확인

npm run build

eslint-plugin-react-hooksexhaustive-deps 경고가 있으면 누락되거나 잘못된 의존성이 있다는 신호다. 경고를 eslint-disable로 억제하기보다 의존성을 올바르게 정리하는 편이 낫다.