본문 바로가기

개발/FRONT

⚠️ Can't perform a React state update on an unmounted component / Cannot read property 'name' of undefined React 오류 정리

728x90

 

React에서 비동기 작업(API 호출, Promise, async/await)을 다룰 때 자주 발생하는 오류들을 정리했습니다. 각 오류의 원인, 오류 메시지, 해결방법을 구체적인 코드 예제와 함께 설명합니다.


1. ⚠️ 언마운트된 컴포넌트에서 상태 업데이트 오류

❌ 오류 메시지

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

🔍 원인 설명

컴포넌트가 언마운트된 후에도 비동기 작업(API 호출 등)이 완료되어 상태를 업데이트하려고 할 때 발생합니다. 이는 메모리 누수로 이어질 수 있습니다.

❌ 문제가 있는 코드

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    // 비동기 작업
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        // 컴포넌트가 언마운트된 후에도 실행될 수 있음
        setUser(data);  // ⚠️ 오류 발생 가능
      });
  }, [userId]);
  
  return
{user?.name}
;
}

✅ 해결방법 1: cleanup 함수 사용

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

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const isMountedRef = useRef(true);
  
  useEffect(() => {
    isMountedRef.current = true;
    
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        // 컴포넌트가 마운트되어 있을 때만 상태 업데이트
        if (isMountedRef.current) {
          setUser(data);
        }
      });
    
    // cleanup 함수
    return () => {
      isMountedRef.current = false;
    };
  }, [userId]);
  
  return
{user?.name}
;
}

✅ 해결방법 2: AbortController 사용

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    const abortController = new AbortController();
    
    fetch(`/api/users/${userId}`, {
      signal: abortController.signal
    })
      .then(res => res.json())
      .then(data => setUser(data))
      .catch(err => {
        if (err.name !== 'AbortError') {
          console.error(err);
        }
      });
    
    // cleanup 함수에서 요청 취소
    return () => {
      abortController.abort();
    };
  }, [userId]);
  
  return
{user?.name}
;
}

2. ⚠️ 비동기 데이터 접근 오류 (undefined 읽기)

❌ 오류 메시지

TypeError: Cannot read property 'name' of undefined

TypeError: Cannot read properties of undefined (reading 'map')

🔍 원인 설명

비동기 작업이 완료되기 전에 데이터에 접근하려고 할 때 발생합니다. 초기 렌더링 시 데이터가 null 또는 undefined인 상태에서 속성에 접근하면 오류가 발생합니다.

❌ 문제가 있는 코드

import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState(null);
  
  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => setUsers(data));
  }, []);
  
  // users가 null일 때 map() 호출 시 오류 발생
  return (
    {users.map(user => ( // ⚠️ TypeError 발생
  • {user.name}
  • ))}
  );
}

✅ 해결방법 1: 옵셔널 체이닝과 기본값 사용

import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState(null);
  
  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => setUsers(data));
  }, []);
  
  // 옵셔널 체이닝과 기본값 사용
  return (
    {(users || []).map(user => (
  • {user.name}
  • ))}
  );
}

✅ 해결방법 2: 조건부 렌더링

import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => {
        setUsers(data);
        setLoading(false);
      });
  }, []);
  
  if (loading) {
    return
Loading...
;
  }
  
  if (!users || users.length === 0) {
    return
No users found
;
  }
  
  return (
    {users.map(user => (
  • {user.name}
  • ))}
  );
}

3. ⚠️ 무한 업데이트 루프 오류

❌ 오류 메시지

Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

🔍 원인 설명

useEffect의 의존성 배열이 잘못 설정되어 무한 루프가 발생합니다. 또는 상태 업데이트가 다시 렌더링을 트리거하여 계속 반복되는 경우입니다.

❌ 문제가 있는 코드

import { useState, useEffect } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(null);
  
  useEffect(() => {
    // data가 변경될 때마다 실행
    fetch('/api/data')
      .then(res => res.json())
      .then(result => {
        setData(result);  // data 변경
        setCount(count + 1);  // count 변경
      });
  }, [data, count]);  // ⚠️ data와 count가 의존성에 포함되어 무한 루프 발생
  
  return
Count: {count}
;
}

✅ 해결방법 1: 의존성 배열 수정

import { useState, useEffect } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(result => {
        setData(result);
        setCount(prev => prev + 1);  // 함수형 업데이트 사용
      });
  }, []);  // ✅ 빈 배열로 마운트 시 한 번만 실행
  
  return
Count: {count}
;
}

✅ 해결방법 2: useCallback 사용

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

function Counter() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(null);
  
  const fetchData = useCallback(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(result => {
        setData(result);
        setCount(prev => prev + 1);
      });
  }, []);  // 의존성 없음
  
  useEffect(() => {
    fetchData();
  }, [fetchData]);
  
  return
Count: {count}
;
}

4. ⚠️ Promise 객체를 직접 렌더링하는 오류

❌ 오류 메시지

Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

🔍 원인 설명

Promise 객체를 직접 JSX에 렌더링하려고 할 때 발생합니다. React는 Promise 객체를 렌더링할 수 없습니다.

❌ 문제가 있는 코드

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  const fetchUser = async () => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  };
  
  // Promise 객체를 직접 반환
  const userPromise = fetchUser();  // ⚠️ Promise 객체
  
  return (
{userPromise} {/* ⚠️ 오류 발생 */}
  );
}

✅ 해결방법: useEffect에서 Promise 처리

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchUser = async () => {
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        setUser(data);  // ✅ Promise 결과를 상태에 저장
      } catch (error) {
        console.error('Error fetching user:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUser();
  }, [userId]);
  
  if (loading) return
Loading...
;
  if (!user) return
User not found
;
  
  return (
{user.name} {/* ✅ 상태 값 렌더링 */}
  );
}

5. ⚠️ useEffect 의존성 배열 경고

❌ 오류 메시지

React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array.

🔍 원인 설명

useEffect 내부에서 사용하는 함수나 변수가 의존성 배열에 포함되지 않아 발생하는 경고입니다. 이는 오래된 클로저 문제를 일으킬 수 있습니다.

❌ 문제가 있는 코드

function UserList({ userId }) {
  const [users, setUsers] = useState([]);
  
  const fetchData = async () => {
    const response = await fetch(`/api/users?userId=${userId}`);
    const data = await response.json();
    setUsers(data);
  };
  
  useEffect(() => {
    fetchData();
  }, []);  // ⚠️ fetchData와 userId가 의존성에 없음
  
  return
{/* ... */}
;
}

✅ 해결방법 1: useCallback 사용

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

function UserList({ userId }) {
  const [users, setUsers] = useState([]);
  
  const fetchData = useCallback(async () => {
    const response = await fetch(`/api/users?userId=${userId}`);
    const data = await response.json();
    setUsers(data);
  }, [userId]);  // ✅ userId를 의존성에 포함
  
  useEffect(() => {
    fetchData();
  }, [fetchData]);  // ✅ fetchData를 의존성에 포함
  
  return
{/* ... */}
;
}

✅ 해결방법 2: useEffect 내부에서 함수 정의

import { useState, useEffect } from 'react';

function UserList({ userId }) {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(`/api/users?userId=${userId}`);
      const data = await response.json();
      setUsers(data);
    };
    
    fetchData();
  }, [userId]);  // ✅ userId를 의존성에 포함
  
  return
{/* ... */}
;
}

6. ⚠️ Promise 체이닝 오류

❌ 오류 메시지

TypeError: Cannot read property 'then' of undefined

🔍 원인 설명

Promise를 반환하지 않는 함수에 .then()을 호출하거나, 비동기 함수에서 값을 반환하지 않아 발생합니다.

❌ 문제가 있는 코드

function fetchUserData(userId) {
  fetch(`/api/users/${userId}`)
    .then(res => res.json())
    // ⚠️ return이 없어서 undefined 반환
}

function UserProfile({ userId }) {
  useEffect(() => {
    fetchUserData(userId)
      .then(data => {  // ⚠️ undefined에 .then() 호출
        console.log(data);
      });
  }, [userId]);
  
  return
User Profile
;
}

✅ 해결방법 1: return 추가

function fetchUserData(userId) {
  return fetch(`/api/users/${userId}`)  // ✅ return 추가
    .then(res => res.json());
}

function UserProfile({ userId }) {
  useEffect(() => {
    fetchUserData(userId)
      .then(data => {
        console.log(data);
      });
  }, [userId]);
  
  return
User Profile
;
}

✅ 해결방법 2: async/await 사용

async function fetchUserData(userId) {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();  // ✅ Promise 반환
}

function UserProfile({ userId }) {
  useEffect(() => {
    const loadData = async () => {
      const data = await fetchUserData(userId);
      console.log(data);
    };
    loadData();
  }, [userId]);
  
  return
User Profile
;
}

7. ⚠️ Promise 에러 처리 누락

❌ 오류 메시지

Uncaught (in promise) Error: Failed to fetch

🔍 원인 설명

Promise의 에러를 처리하지 않아 발생합니다. 네트워크 오류나 서버 오류 시 사용자에게 적절한 피드백을 제공하지 못합니다.

❌ 문제가 있는 코드

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
      // ⚠️ .catch()가 없어서 에러 처리 안 됨
  }, [userId]);
  
  return
{user?.name}
;
}

✅ 해결방법 1: .catch() 추가

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => {
        if (!res.ok) {
          throw new Error('Failed to fetch user');
        }
        return res.json();
      })
      .then(data => setUser(data))
      .catch(err => {
        setError(err.message);  // ✅ 에러 처리
        console.error('Error:', err);
      });
  }, [userId]);
  
  if (error) return
Error: {error}
;
  if (!user) return
Loading...
;
  
  return
{user.name}
;
}

✅ 해결방법 2: try-catch 사용 (async/await)

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        
        if (!response.ok) {
          throw new Error('Failed to fetch user');
        }
        
        const data = await response.json();
        setUser(data);
        setError(null);
      } catch (err) {
        setError(err.message);  // ✅ 에러 처리
        console.error('Error:', err);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUser();
  }, [userId]);
  
  if (loading) return
Loading...
;
  if (error) return
Error: {error}
;
  if (!user) return
User not found
;
  
  return
{user.name}
;
}

✅ 마무리

React에서 비동기 작업을 다룰 때 발생하는 주요 오류 7가지를 정리했습니다. 각 오류의 원인과 해결방법을 이해하면 더 안정적인 React 애플리케이션을 개발할 수 있습니다.

주요 포인트:

  • cleanup 함수 사용: 언마운트된 컴포넌트에서 상태 업데이트 방지
  • 조건부 렌더링: 비동기 데이터 접근 전 null 체크
  • 의존성 배열 관리: useEffect 의존성 올바르게 설정
  • 에러 처리: 모든 Promise에 .catch() 또는 try-catch 추가
  • 로딩 상태 관리: 비동기 작업 중 로딩 상태 표시

💡 참고: React 18부터는 자동 배치(Automatic Batching)가 개선되어 여러 상태 업데이트가 자동으로 배치됩니다. 또한 Suspense와 함께 사용하면 비동기 데이터 로딩을 더 효율적으로 처리할 수 있습니다.

 

카카오톡 오픈채팅 링크

https://open.kakao.com/o/seCteX7h

 


 

728x90