When our spaceship sends a request to Mission Control, several things can happen: the data is on its way (loading), the transmission failed (error), or the center has no data to send (empty result). We must handle each of these situations!
The most popular pattern in React for handling data fetching uses three state variables:
1import React, { useState, useEffect } from 'react';
2
3function StarshipCatalog() {
4 const [data, setData] = useState(null);
5 const [loading, setLoading] = useState(true);
6 const [error, setError] = useState(null);
7
8 useEffect(() => {
9 async function fetchStarships() {
10 try {
11 setLoading(true);
12 setError(null);
13
14 const response = await fetch(
15 'https://swapi.dev/api/starships/'
16 );
17
18 if (!response.ok) {
19 throw new Error(\`HTTP Error: \${response.status}\`);
20 }
21
22 const result = await response.json();
23 setData(result.results);
24 } catch (err) {
25 setError(err.message);
26 } finally {
27 setLoading(false);
28 }
29 }
30
31 fetchStarships();
32 }, []);
33
34 // Loading state
35 if (loading) {
36 return <div className="loading">Scanning the sector...</div>;
37 }
38
39 // Error state
40 if (error) {
41 return <div className="error">Communication error: {error}</div>;
42 }
43
44 // Empty data state
45 if (!data || data.length === 0) {
46 return <div className="empty">No ships found in this sector</div>;
47 }
48
49 // Data ready to display
50 return (
51 <div>
52 <h2>Ships detected: {data.length}</h2>
53 {data.map(ship => (
54 <div key={ship.name}>
55 <h3>{ship.name}</h3>
56 <p>Model: {ship.model}</p>
57 </div>
58 ))}
59 </div>
60 );
61}We can create nicer components for each state:
1function LoadingSpinner() {
2 return (
3 <div className="spinner-container">
4 <div className="spinner"></div>
5 <p>Loading data from mission control...</p>
6 </div>
7 );
8}
9
10function ErrorMessage({ message, onRetry }) {
11 return (
12 <div className="error-container">
13 <h3>Connection lost!</h3>
14 <p>{message}</p>
15 <button onClick={onRetry}>
16 Retry
17 </button>
18 </div>
19 );
20}
21
22function EmptyState() {
23 return (
24 <div className="empty-container">
25 <p>No data in this galactic quadrant</p>
26 <p>Try scanning a different sector</p>
27 </div>
28 );
29}When communication with the server fails, it's worth giving the user the option to retry:
1function SpaceData() {
2 const [data, setData] = useState(null);
3 const [loading, setLoading] = useState(true);
4 const [error, setError] = useState(null);
5
6 const fetchData = async () => {
7 try {
8 setLoading(true);
9 setError(null);
10 const response = await fetch(
11 'https://swapi.dev/api/people/'
12 );
13 if (!response.ok) throw new Error('Network error');
14 const result = await response.json();
15 setData(result.results);
16 } catch (err) {
17 setError(err.message);
18 } finally {
19 setLoading(false);
20 }
21 };
22
23 useEffect(() => {
24 fetchData();
25 }, []);
26
27 if (loading) return <LoadingSpinner />;
28 if (error) return <ErrorMessage message={error} onRetry={fetchData} />;
29 if (!data || data.length === 0) return <EmptyState />;
30
31 return (
32 <ul>
33 {data.map(person => (
34 <li key={person.name}>{person.name}</li>
35 ))}
36 </ul>
37 );
38}The key difference: the
fetchData function is defined outside useEffect, so we can call it again from the "Retry" button.Instead of a simple spinner, you can display an interface skeleton:
1function SkeletonCard() {
2 return (
3 <div className="skeleton-card">
4 <div className="skeleton-title"></div>
5 <div className="skeleton-text"></div>
6 <div className="skeleton-text short"></div>
7 </div>
8 );
9}
10
11function PlanetCatalog() {
12 const [planets, setPlanets] = useState(null);
13 const [loading, setLoading] = useState(true);
14
15 // ... fetch logic ...
16
17 if (loading) {
18 return (
19 <div>
20 <SkeletonCard />
21 <SkeletonCard />
22 <SkeletonCard />
23 </div>
24 );
25 }
26
27 return (
28 <div>
29 {planets.map(planet => (
30 <PlanetCard key={planet.name} planet={planet} />
31 ))}
32 </div>
33 );
34}Remember -- each state (loading, error, success, empty) requires dedicated UI. Good state handling is the difference between a professional application and an amateur project. The user should never stare at a blank screen without information about what's happening!