useEffect에서 객체와 배열로 인한 무한 루프 해결 방법
출처: Dev.to
객체와 배열을 사용한 useEffect 무한 루프 해결 방법
기술적 설명
문제는 useEffect가 의존성 값을 참조 비교(===)로만 확인하고, 내용 비교(deep equality)를 하지 않기 때문에 발생합니다. useState({})를 사용할 때마다 setObj({}) 호출은 메모리 상에 새로운 객체를 만들지만 내용은 동일합니다. React는 참조가 바뀌었다고 판단(obj !== obj)해서 useEffect를 다시 실행하고, 이 과정이 무한 루프를 일으킵니다.
예시:
const [ingredients, setIngredients] = useState({});
useEffect(() => {
setIngredients({}); // 새로운 객체를 생성!
}, [ingredients]); // ingredients가 참조가 바뀌어 → 재실행 → 무한 루프
1️⃣ 의존성을 제거하고 빈 배열 사용하기
useEffect 안에서 상태를 업데이트할 필요가 없다면, 객체/배열 의존성을 없애고 빈 배열을 사용합니다.
useEffect(() => {
// 컴포넌트가 마운트될 때 한 번만 실행
setIngredients({});
}, []); // ✅ 의존성 없음 → 한 번만 실행
2️⃣ 내용이 바뀔 때만 실행하고 싶다면 JSON.stringify 사용
단순 객체(함수, 순환 참조가 없는 경우)라면 JSON.stringify로 내용 비교를 할 수 있습니다.
useEffect(() => {
setIngredients({});
}, [JSON.stringify(ingredients)]); // ✅ 내용 비교, 참조 비교 아님
⚠️ 주의:
JSON.stringify는 큰 객체에서는 비용이 많이 들고, 함수·undefined·순환 참조가 있는 경우 오류가 발생합니다.
3️⃣ useMemo 로 안정적인 표현값 만들기
객체를 직접 의존성에 두는 대신, 안정적인 원시값(예: 요소 개수, 해시)으로 변환합니다.
const ingredientsHash = useMemo(() =>
Object.keys(ingredients).length, // 혹은 커스텀 로직
[ingredients]
);
useEffect(() => {
setIngredients({});
}, [ingredientsHash]); // ✅ 안정적인 원시값 비교
4️⃣ 내용이 동일하면 업데이트 하지 않기
내용이 변하지 않았을 때는 setState를 호출하지 않도록 조건을 추가합니다.
useEffect(() => {
if (Object.keys(ingredients).length > 0) {
setIngredients({});
}
}, [ingredients]); // ✅ 비어 있지 않을 때만 업데이트
5️⃣ 최종 해결책 (의존성 없이 한 번만 실행)
const [ingredients, setIngredients] = useState({});
useEffect(() => {
// 컴포넌트가 마운트될 때만 실행 (예: 초기 상태 정리)
setIngredients({});
}, []); // ✅ 이 경우 가장 확실한 해결법
핵심: 의존성으로 사용한 동일한 상태를 깊은 비교 없이 바로 업데이트하면 무한 루프가 발생합니다.
추가 팁
- 복잡한 객체라면 immer 혹은 use-deep-compare-effect(
useDeepCompareEffect) 같은 라이브러리를 사용해 안전하게 깊은 비교를 수행하세요. - 목표가 마운트 시 상태를 초기화하는 것이라면,
useEffect([])를 쓰고 내부에서 상태를 바꾸지 말고, 초기값을useState에 바로 넣는 것이 가장 깔끔합니다.
const [ingredients, setIngredients] = useState({}); // ✅ 초기값 직접 지정