React 컴포넌트에서 무한 루프는 주로 useEffect 내에서 객체(Object)나 배열(Array) 상태를 잘못 업데이트할 때 발생합니다.
React 렌더링 과정: 상태 변경 → 컴포넌트 재실행(리렌더링) → useEffect 의존성 비교
무한 루프의 원인:
useEffect 내부에서 setState로 새로운 객체를 생성하여 상태를 업데이트합니다. 상태가 변경되었으므로 리렌더링이 발생합니다.
리렌더링 후 useEffect가 다시 실행되면서 또다시 새로운 객체를 생성하여 setState를 호출합니다.
1번으로 돌아가 무한 반복됩니다.
JavaScript에서 { a: 1 }과 { a: 1 }은 내용이 같아도 서로 다른 메모리 주소를 가진 별개의 객체입니다. React는 이 주소(참조)가 다르기 때문에 상태가 변경되었다고 판단하고 리렌더링을 계속 발생시킵니다.
// ❌ 무한 루프가 발생하는 코드 예시
useEffect(() => {
// 렌더링될 때마다 '새로운' 객체가 생성되어 상태가 계속 변경됨
setSomeObject({ key: 'value' });
}, [someObject]); // 의존성 배열에 객체 자신이 포함되어 있는 경우
반면, boolean, number, string과 같은 원시값은 값이 같으면 React가 리렌더링을 생략합니다.
// ✅ 괜찮은 코드 예시
// device.powerMode가 'on'으로 동일하다면 계속 true가 전달됨
setPowerStatus(device.powerMode === 'on');
// React는 기존 상태가 true인데 또 true로 업데이트하라는 요청을 받으면
// 상태 변경이 없다고 판단하고 리렌더링을 하지 않습니다.
객체 상태, 어떻게 안전하게 다룰까? ✅
객체 상태를 업데이트하면서 무한 루프를 피하는 핵심은 "의존성이 실제로 변경되었을 때만 새로운 객체를 생성"하는 것입니다.
useMemo는 의존성 배열의 값이 변경될 때만 새로운 객체를 생성하고, 그렇지 않으면 이전에 생성했던 객체를 그대로 반환합니다.
장점: 코드가 깔끔하고, 의도치 않은 객체 재생성을 막아 성능 최적화와 무한 루프 방지에 효과적입니다. device 같은 값이 초기 로딩 시 undefined여도 안전하게 처리됩니다.
// device의 특정 속성들이 바뀔 때만 softApData 객체가 새로 생성됨
const softApData = useMemo(() => ({
softApMode: device?.softApMode || 0,
softApPassword: device?.softApPassword || '',
// ...
}), [device?.softApMode, device?.softApPassword]); // 실제 값들을 의존성으로 지정
useEffect(() => {
setSoftApPopupData(softApData);
}, [softApData]); // softApData의 참조가 바뀔 때만 실행됨
상태를 업데이트하기 전에 새로운 데이터와 이전 데이터를 비교하여, 실제로 내용이 다를 때만 업데이트를 실행하는 방법입니다.
단점: 객체가 복잡하면 비교 로직(예: JSON.stringify)이 번거롭고 성능에 부담을 줄 수 있습니다.
하나의 큰 객체 대신 여러 개의 useState를 사용하여 각 속성을 개별적인 상태로 관리합니다. 이는 객체 참조 문제 자체를 원천적으로 방지합니다.
단점: 관리해야 할 상태가 너무 많아지면 코드가 복잡해질 수 있습니다.
React에서 객체 상태를 다룰 때는 참조 동일성(Reference Equality)을 이해하는 것이 무한 루프를 방지하는 핵심입니다. useMemo는 이러한 문제를 가장 우아하고 효율적으로 해결해 주는 최고의 방법입니다.