로딩 상태와 에러 처리
비동기 데이터 가져오기 과정에서 로딩 상태 관리와 에러 처리는 사용자 경험을 향상시키는 데 중요한 역할을 합니다.
이 절에서는 React 애플리케이션에서 이러한 상황을 효과적으로 다루는 방법을 살펴보겠습니다.
로딩 상태 관리
로딩 상태를 관리하는 가장 기본적인 방법은 state를 사용하는 것입니다.
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUser() {
setLoading(true);
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (error) {
console.error('Error fetching user:', error);
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]);
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}
이 예제에서는 loading
상태를 사용하여 데이터 가져오기 진행 중임을 표시합니다.
데이터 요청이 시작될 때 loading
을 true
로 설정하고, 요청이 완료되면 false
로 설정합니다.
로딩 인디케이터 구현
사용자에게 더 나은 시각적 피드백을 제공하기 위해 커스텀 로딩 인디케이터를 구현할 수 있습니다.
function LoadingSpinner() {
return (
<div className="spinner">
<div className="bounce1"></div>
<div className="bounce2"></div>
<div className="bounce3"></div>
</div>
);
}
function UserProfile({ userId }) {
// ... 이전 코드와 동일 ...
if (loading) {
return <LoadingSpinner />;
}
// ... 나머지 코드 ...
}
이 LoadingSpinner
컴포넌트는 CSS 애니메이션을 사용하여 동적인 로딩 표시를 제공할 수 있습니다.
에러 처리
에러 처리는 try-catch 문을 사용하여 구현할 수 있습니다.
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchUser() {
setLoading(true);
setError(null);
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
const data = await response.json();
setUser(data);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]);
if (loading) {
return <LoadingSpinner />;
}
if (error) {
return <div>Error: {error}</div>;
}
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}
이 예제에서는 에러가 발생하면 error
상태를 설정하고, 이를 사용자에게 표시합니다.
네트워크 에러 처리
네트워크 에러와 같은 특정 에러 상황에 대해 더 구체적인 처리를 할 수 있습니다.
async function fetchUser() {
setLoading(true);
setError(null);
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
if (response.status === 404) {
throw new Error('User not found');
} else {
throw new Error('An error occurred while fetching the user');
}
}
const data = await response.json();
setUser(data);
} catch (error) {
if (error instanceof TypeError && error.message === 'Failed to fetch') {
setError('Network error. Please check your internet connection.');
} else {
setError(error.message);
}
} finally {
setLoading(false);
}
}
이 예제에서는 서버 응답 상태 코드와 네트워크 오류를 구분하여 처리합니다.
에러 경계(Error Boundaries) 구현
컴포넌트 트리 전체에 대한 에러 처리를 위해 에러 경계 컴포넌트를 구현할 수 있습니다.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.log('Error caught by ErrorBoundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<UserProfile userId={1} />
</ErrorBoundary>
);
}
에러 경계는 하위 컴포넌트 트리에서 발생하는 JavaScript 에러를 포착하고, 대체 UI를 표시합니다.
재시도 메커니즘 구현
일시적인 네트워크 문제를 해결하기 위해 재시도 메커니즘을 구현할 수 있습니다.
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
async function fetchUserWithRetry(retries = 3) {
setLoading(true);
setError(null);
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
const data = await response.json();
setUser(data);
return;
} catch (error) {
console.log(`Attempt ${i + 1} failed. Retrying...`);
if (i === retries - 1) {
setError('Failed to fetch user after multiple attempts');
}
}
}
setLoading(false);
}
useEffect(() => {
fetchUserWithRetry();
}, [userId]);
if (loading) return <LoadingSpinner />;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}
이 예제에서는 데이터 가져오기에 실패할 경우 지정된 횟수만큼 재시도합니다.
로딩 상태 관리와 에러 처리는 견고한 React 애플리케이션을 구축하는 데 필수적입니다.
적절한 로딩 인디케이터를 사용하면 사용자에게 작업이 진행 중임을 알려줄 수 있고, 체계적인 에러 처리를 통해 문제가 발생했을 때 사용자에게 명확한 피드백을 제공할 수 있습니다.
다양한 에러 상황(네트워크 오류, 서버 오류, 데이터 형식 오류 등)에 대비하고, 각 상황에 맞는 적절한 메시지를 표시하는 것이 중요합니다.
또한, 에러 경계를 사용하여 예상치 못한 에러로부터 애플리케이션을 보호하고, 가능한 경우 재시도 메커니즘을 구현하여 일시적인 문제를 해결할 수 있습니다.
이러한 기법들을 적절히 조합하여 사용하면, 더 안정적이고 사용자 친화적인 React 애플리케이션을 개발할 수 있습니다.