목요일

React useEffect 무한루프 문제 해결

🔍 검색 키워드: React useEffect 무한루프, useEffect 의존성 배열, useEffect 성능 최적화, React 렌더링 무한 반복

React useEffect 무한루프 문제 해결

증상

import { useEffect, useState } from 'react';

export default function DataFetcher() { const [data, setData] = useState([]);

useEffect(() => { fetch('/api/data') .then(res => res.json()) .then(data => setData(data)); }); // ⚠️ 의존성 배열 없음 return <div>{data.length} items</div>; }

결과: 컴포넌트가 무한히 렌더링되고 API 요청이 계속 발생하며 브라우저 성능이 급격히 저하됩니다.

원인

React 18 Strict Mode에서 개발 환경 시 useEffectꊔ 의도적으로 2회 실행되지만, 가장 흔한 무한루프 원인은 의존성 배열이 생략되거나 빈 배열이 아닌 경우입니다.

- 의존성 배열 없음: 렌더링될 때마다 effect가 실행 → state 업데이트 → 다시 렌더링 → 무한 루프 - 객체/배열을 의존성으로 사용: 매 렌더링마다 새 참조 생성 → effect 재실행 → 무한 루프

// ❌ 나쁜 예: effect 내부에서 생성한 객체를 의존성으로 사용
useEffect(() => {
  const config = { timeout: 5000 };
  setData(config);
}, [config]); // config는 매번 새로 생성됨

// ❌ 나쁜 예: 함수를 의존성으로 사용 const fetchData = () => fetch('/api'); useEffect(() => { fetchData(); }, [fetchData]); // fetchData는 매번 새로 생성됨

해결방법

1. 의존성 배열 추가 (기본 해결)

import { useEffect, useState } from 'react';

export default function DataFetcher() { const [data, setData] = useState([]);

useEffect(() => { fetch('/api/data') .then(res => res.json()) .then(data => setData(data)); }, []); // ✅ 마운트 시에만 실행 return <div>{data.length} items</div>; }

2. 객체/배열 의존성 안정화

// ❌ useEffect 내부에서 객체 생성
useEffect(() => {
  const options = { headers: { 'Authorization': token } };
  fetchWithOptions(options);
}, []); // 작동하지만 options는 매번 재생성

// ✅ useMemo로 안정화 const options = useMemo(() => ({ headers: { 'Authorization': token } }), [token] );

useEffect(() => { fetchWithOptions(options); }, [options]);

3. 함수 의존성 안정화 (useCallback)

// ❌ fetchData 매번 재생성
const fetchData = () => {
  return fetch('/api/data').then(r => r.json());
};

useEffect(() => { fetchData(); }, [fetchData]);

// ✅ useCallback으로 메모이제이션 const fetchData = useCallback(() => { return fetch('/api/data').then(r => r.json()); }, []);

useEffect(() => { fetchData(); }, [fetchData]);

4. 실전 예제: API 데이터 페칭

import { useEffect, useState, useCallback } from 'react';

export default function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);

useEffect(() => { let isMounted = true; // 언마운트 후 state 업데이트 방지

const fetchUser = async () => { try { setLoading(true); const response = await fetch(/api/users/${userId}); const data = await response.json(); if (isMounted) { setUser(data); setError(null); } } catch (err) { if (isMounted) { setError(err.message); setUser(null); } } finally { if (isMounted) { setLoading(false); } } };

fetchUser();

return () => { isMounted = false; // 정리 함수: 언마운트 시 플래그 설정 }; }, [userId]); // userId 변경 시에만 재실행

if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error}</p>; return <div>{user?.name}</div>; }

정리표

상황 원인 해결책
의존성 배열 없음 매 렌더링마다 effect 재실행 [] 추가하거나 올바른 의존성 지정
객체를 의존성으로 사용 참조 변경으로 매번 재실행 useMemo로 메모이제이션
함수를 의존성으로 사용 새 함수 참조로 무한 루프 useCallback으로 메모이제이션
state setter 직접 의존성 불필요한 재실행 의존성에서 제거 (setter는 안정함)
외부 변수를 직접 사용 ESLint 경고 무시 시 버그 모든 의존성을 배열에 포함
핵심: useEffect7�va 의존성 배열 시 따르면 대부분의 무한루프를 방지할 수 있습니다.

댓글 없음:

댓글 쓰기