본문 바로가기
카테고리 없음

React useEffect 무한루프 해결법 - 실전 경험 기반 가이드

by 트티 2025. 4. 8.

React useEffect 무한루프 해결법 - 실전 경험 기반 가이드

React를 사용하면서 useEffect 훅을 도입한 후, 예상치 못한 무한 렌더링 또는 무한루프 문제에 부딪혀본 경험이 있나요? 이 문제는 초보 개발자뿐만 아니라 숙련된 개발자에게도 흔히 발생하는 이슈 중 하나입니다. 특히 상태(state)나 props의 의존성 배열을 제대로 다루지 않으면 useEffect는 계속해서 실행되며, 성능 저하나 브라우저 다운까지 초래할 수 있어요.

이번 글에서는 제가 실무에서 경험했던 사례를 바탕으로, useEffect 무한루프가 발생하는 이유와 그 해결 방법을 구체적인 예제 코드와 함께 설명드릴게요.

목차

1. useEffect 무한루프 현상이란?

React의 useEffect 훅은 컴포넌트가 렌더링된 이후 특정 작업을 수행하도록 하는 데 사용됩니다. 하지만 이 훅을 잘못 사용하면 상태가 변경될 때마다 다시 렌더링되면서 무한히 실행되는 현상이 발생할 수 있어요.

{`
useEffect(() => {
    setCount(count + 1); // 상태 변경
}, [count]);
        `}

위 예제는 count 값이 변경될 때마다 setCount가 호출되고, 다시 count가 변경되면서 useEffect가 무한히 호출되는 대표적인 경우입니다.

2. 의존성 배열(Dependency Array)의 역할

useEffect 훅에서 가장 중요한 요소 중 하나는 바로 의존성 배열</strong입니다. 이 배열은 useEffect가 언제 실행되어야 하는지를 결정하는 역할을 해요.

예를 들어, 아래 코드를 살펴볼게요.

{`
useEffect(() => {
    console.log("데이터 불러오기");
}, []);
        `}

의존성 배열이 빈 배열이라면, 해당 useEffect는 처음 렌더링 시 단 한 번만 실행됩니다. 하지만 배열에 어떤 상태나 props가 들어가 있으면, 그 값이 바뀔 때마다 useEffect가 다시 실행돼요.

3. 실제 사례로 보는 무한루프 예제

제가 실제로 경험했던 상황 중 하나는, API에서 데이터를 불러오고 그것을 상태로 저장하는 코드였어요. 처음에는 문제가 없어 보였지만, 의존성 배열에 상태값을 잘못 넣는 바람에 무한루프에 빠졌습니다.

{`
const [data, setData] = useState([]);

useEffect(() => {
    fetch("/api/data")
        .then(res => res.json())
        .then(result => setData(result));
}, [data]); // ❌ 잘못된 의존성
        `}

이 경우, setData로 상태가 바뀌면 data 값이 변경되며 useEffect가 다시 실행되고, 또 setData가 호출되면서 무한루프가 발생해요.

이런 실수를 방지하기 위해서는 다음과 같이 의존성 배열을 조정해야 합니다.

{`
useEffect(() => {
    fetch("/api/data")
        .then(res => res.json())
        .then(result => setData(result));
}, []); // ✅ 올바른 의존성: 최초 한 번만 실행
        `}

또는 함수 내부에서 직접적으로 사용하는 값들만 의존성 배열에 넣는 것이 중요합니다.

4. 문제 해결을 위한 체크리스트

  • 의존성 배열에 상태 변경 함수(setState 등)를 절대 넣지 않는다.
  • useEffect 안에서 사용하는 변수만 의존성 배열에 포함시킨다.
  • 비동기 함수는 useEffect 외부에 정의하거나 useCallback과 함께 사용한다.
  • eslint-plugin-react-hooks의 경고를 잘 확인하고 무시하지 않는다.

이제 다음 단계에서는 이 문제를 방지하기 위한 고급 전략과 예측, 그리고 제가 직접 실무에서 사용한 팁을 소개할게요.

위에서 기본적인 문제 상황과 해결법을 알아보았다면, 이제는 조금 더 고급 전략으로 넘어가 볼게요. 무한루프를 피하기 위해 단순히 빈 배열을 넣는 것이 항상 정답은 아니기 때문에, 상황에 따라 정교한 설계가 필요합니다.

의존성 배열을 정교하게 다루는 팁

실무에서는 종종 의존성 배열에 많은 값을 넣어야 할 상황이 생겨요. 이때는 불변성 유지참조값 변화를 주의해야 합니다. 예를 들어 객체나 배열을 의존성 배열에 넣는다면, 값의 변경이 없어도 매 렌더링마다 새롭게 생성되므로 useEffect가 다시 실행될 수 있어요.

{`
useEffect(() => {
    console.log("실행됨");
}, [filters]); // filters가 매번 새롭게 생성되면 무한루프 위험!
        `}

이 경우에는 useMemo를 활용하여 참조값이 불필요하게 변경되지 않도록 최적화해야 합니다.

{`
const memoizedFilters = useMemo(() => filters, [filters]);

useEffect(() => {
    console.log("필터 변경 감지");
}, [memoizedFilters]);
        `}

고급 전략: useRef와 useCallback의 활용

렌더링에 영향을 주지 않으면서 값을 저장하고 싶다면 useRef가 좋은 대안이 될 수 있어요. 예를 들어, 이전 값을 추적하거나 외부 이벤트 리스너 등을 관리할 때 무한루프를 방지할 수 있습니다.

{`
const prevValue = useRef();

useEffect(() => {
    if (prevValue.current !== value) {
        prevValue.current = value;
        console.log("값이 변경되었습니다.");
    }
}, [value]);
        `}

또한, 콜백 함수를 자주 props로 전달할 경우, 매번 새로 생성되는 함수를 방지하기 위해 useCallback을 활용하면 useEffect의 의존성 배열도 안정적으로 유지할 수 있어요.

{`
const handleClick = useCallback(() => {
    console.log("클릭됨");
}, []);

useEffect(() => {
    window.addEventListener("click", handleClick);
    return () => window.removeEventListener("click", handleClick);
}, [handleClick]);
        `}

5. 마무리 및 팁

React의 useEffect는 매우 강력하지만, 그만큼 신중하게 다뤄야 하는 도구입니다. 의존성 배열을 어떻게 구성하느냐에 따라 성능도, 안정성도 달라져요.

제가 경험한 바로는, 코드를 작성할 때마다 다음과 같은 질문을 스스로에게 던져보는 것이 좋았어요:

  • 이 useEffect는 정확히 언제 실행되어야 할까?
  • 의존성 배열에 넣은 값은 참조가 자주 바뀌는 객체/배열인가?
  • 렌더링과 무관한 값을 관리하고 있는가?

이러한 질문들을 스스로에게 던지며 코드를 작성하면, 무한루프 문제는 점점 줄어들게 됩니다. 마지막으로, useEffect 사용 시 자주 하는 실수를 정리하며 글을 마무리할게요.

React useEffect 무한루프 방지를 위한 마무리 체크리스트

  • 의존성 배열은 신중하게 구성하고, 상태 변경 함수를 절대 넣지 않는다.
  • 불변성을 유지하기 위한 useMemo, useCallback을 적극 활용한다.
  • 불필요한 렌더링을 피하기 위해 useRef를 사용해 렌더링과 무관한 데이터 관리한다.
  • eslint-plugin-react-hooks의 경고는 피하지 말고 적극적으로 활용한다.

무한루프 문제는 React 개발자라면 누구나 한 번쯤 겪는 필연적인 과정입니다. 하지만 그 원인과 해결책을 정확히 이해하고, 실제 프로젝트에서 반복적으로 적용해보면서 점점 더 안정적인 코드로 나아갈 수 있어요.

useEffect는 단순한 훅이 아닌 리액트의 생명주기를 다루는 핵심 로직입니다. 그래서 한 줄 한 줄이 무한루프의 씨앗이 될 수 있는 만큼, 늘 신중하게 설계하고 테스트하는 습관을 들이는 것이 가장 중요합니다.