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-hooks의 exhaustive-deps 경고가 있으면 누락되거나 잘못된 의존성이 있다는 신호다. 경고를 eslint-disable로 억제하기보다 의존성을 올바르게 정리하는 편이 낫다.