Frontend Study

React # 10 Error handle

jimmmy_jin 2025. 1. 2. 09:51

데이터 Fetching에서 발생할 수 있는 두 가지 주요 에러와 효과적인 관리법

데이터를 Fetching할 때 주로 발생하는 에러는 두 가지로 나뉜다.

  1. 요청 시도 자체가 실패한 경우:
    잘못된 URL, 네트워크 충돌 등으로 인해 요청이 이루어지지 않는 경우이다.
  2. 백엔드에서 에러가 발생한 경우:
    요청은 정상적으로 전달되었지만, 서버 내부에서 문제가 발생하여 에러가 반환되는 경우이다.

1. Fetching을 위한 기본 코드 구조

React의 useEffect 안에서 데이터를 Fetching하는 기본적인 코드 구조는 아래와 같다:

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

function App() {
  const [availablePlaces, setAvailablePlaces] = useState([]); // 데이터 상태
  const [isLoading, setIsLoading] = useState(false); // 로딩 상태
  const [error, setError] = useState(null); // 에러 상태

  useEffect(() => {
    async function fetchPlaces() {
      setIsLoading(true);

      try {
        const response = await fetch('http://localhost:3000/places'); // API 호출
        const resData = await response.json();

        if (!response.ok) {
          // 백엔드 에러 처리
          throw new Error(resData.message || 'Could not fetch places.');
        }

        setAvailablePlaces(resData.places); // 상태 업데이트
      } catch (error) {
        setError({ message: error.message || 'Failed to fetch places' }); // 에러 처리
      }

      setIsLoading(false); // 로딩 종료
    }

    fetchPlaces();
  }, []);

  return (
    <div>
      {isLoading && <p>로딩 중...</p>}
      {error && <p>{error.message}</p>}
      {!isLoading && !error && (
        <ul>
          {availablePlaces.map((place, index) => (
            <li key={index}>{place.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default App;

2. 주요 포인트

  1. useEffect에서 무한 루프 방지:
    데이터 Fetching은 useEffect 안에 넣어 한 번만 실행되도록 한다.
  2. HTTP 응답 상태 코드 확인:
    response.ok를 통해 상태 코드를 확인한다.
    • 성공: 200, 300대 코드
    • 실패: 400, 500대 코드
    실패한 경우, 에러 메시지를 new Error로 생성하여 핸들링한다.
  3. 로딩 및 에러 관리:
    • isLoading 상태로 로딩 중임을 사용자에게 알린다.
    • error 상태로 발생한 에러를 화면에 표시한다.

3. 중복된 Fetching 로직을 Hook으로 분리하기

중복되는 Fetching 로직은 별도의 Hook으로 만들어 관리할 수 있다. 아래는 Fetching을 위한 커스텀 Hook의 예시이다:

Fetching Hook 예시

export async function fetchUserPlaces() {
  const response = await fetch('http://localhost:3000/user-places');
  const resData = await response.json();

  if (!response.ok) {
    throw new Error(resData.message || 'Could not fetch user places.');
  }

  return resData.places; // 데이터 반환
}

Hook 활용 코드

useEffect(() => {
  async function fetchPlaces() {
    setIsLoading(true);
    try {
      const places = await fetchUserPlaces(); // Fetching Hook 호출
      setAvailablePlaces(places);
    } catch (error) {
      setError({ message: error.message || 'Failed to fetch user places' });
    }
    setIsLoading(false);
  }
  fetchPlaces();
}, []);

4. 데이터 업데이트를 위한 Hook 예시

데이터 업데이트는 Fetching과 다르게 HTTP 메서드와 요청 본문(body)에 주의해야 한다. 아래는 업데이트를 처리하는 Hook의 예시이다:

export async function updateUserPlaces(places) {
  const response = await fetch('http://localhost:3000/user-places', {
    method: 'PUT',
    body: JSON.stringify({ places }), // 객체 형태로 전달
    headers: {
      'Content-Type': 'application/json',
    },
  });

  const resData = await response.json();

  if (!response.ok) {
    throw new Error(resData.message || 'Could not update user places.');
  }

  return resData.message; // 성공 메시지 반환
}

중요 포인트

  • 데이터 형태 확인:
    body에 전달하는 데이터의 형태는 백엔드에서 요구하는 구조를 따라야 한다. 예를 들어, places: places 형식으로 객체를 감싸지 않으면 에러가 발생한다.

5. 에러와 데이터 형태에 대한 주의사항

  • GET/DELETE 요청:
    일반적으로 데이터 형태를 크게 신경 쓰지 않아도 된다.
  • POST/PUT 요청:
    데이터를 백엔드에서 요구하는 구조로 정확히 전달해야 한다. 특히, 업데이트 시 전달되는 데이터가 배열인지 객체인지 확인하여 문제를 방지한다.

6. 마무리

데이터 Fetching은 에러 처리, 로딩 상태 관리, 그리고 데이터의 형태를 정확히 이해하는 것이 핵심이다. 중복되는 로직은 Hook으로 분리하여 코드의 재사용성을 높이고, 각 요청의 목적(GET, POST, DELETE)에 맞는 적절한 구조로 작성하는 습관을 들이는 것이 중요하다. 이러한 구조를 익히면 보다 효율적으로 데이터 요청 및 처리를 구현할 수 있다.