Komunikacja z zewnętrznymi API jest jednym z najczęstszych przypadków użycia efektów ubocznych w aplikacjach React. W tym module nauczymy się, jak efektywnie pobierać, przetwarzać i zarządzać danymi z zewnętrznych źródeł.
W aplikacjach webowych, dane często pochodzą z zewnętrznych API, które dostarczają informacje w formacie JSON, XML lub innych. W naszej kosmicznej podróży, możemy wyobrazić sobie centrum kontroli misji pobierające dane z różnych satelitów, statków kosmicznych i stacji badawczych.
1import React, { useState, useEffect } from 'react';
2
3function AstronautsList() {
4 const [astronauts, setAstronauts] = useState([]);
5 const [loading, setLoading] = useState(true);
6 const [error, setError] = useState(null);
7
8 useEffect(() => {
9 // Funkcja asynchroniczna do pobierania danych
10 async function fetchAstronauts() {
11 try {
12 setLoading(true);
13 // Pobieranie danych
14 const response = await fetch('https://api.spaceagency.com/astronauts');
15
16 // Sprawdzenie czy odpowiedź jest OK
17 if (!response.ok) {
18 throw new Error(`HTTP Error: ${response.status}`);
19 }
20
21 // Parsowanie odpowiedzi JSON
22 const data = await response.json();
23
24 // Aktualizacja stanu
25 setAstronauts(data);
26 setError(null);
27 } catch (err) {
28 // Obsługa błędów
29 setError(`Nie udało się pobrać danych: ${err.message}`);
30 setAstronauts([]);
31 } finally {
32 // Oznaczenie zakończenia ładowania
33 setLoading(false);
34 }
35 }
36
37 // Wywołanie funkcji
38 fetchAstronauts();
39 }, []); // Pusta tablica zależności - wykonaj tylko raz
40
41 // Renderowanie na podstawie stanu
42 if (loading) return <div>Ładowanie danych...</div>;
43 if (error) return <div>Błąd: {error}</div>;
44
45 return (
46 <div>
47 <h2>Aktywni astronauci</h2>
48 <ul>
49 {astronauts.map(astronaut => (
50 <li key={astronaut.id}>
51 {astronaut.name} - {astronaut.role}
52 </li>
53 ))}
54 </ul>
55 </div>
56 );
57}W powyższym przykładzie:
useState do przechowywania danych, stanu ładowania i błędówuseEffect z pustą tablicą zależności, aby pobrać dane tylko raz po zamontowaniu komponentuasync/await do obsługi asynchronicznego pobierania danychCzęsto musimy pobierać dane na podstawie parametrów, które mogą się zmieniać:
1function MissionDetails({ missionId }) {
2 const [mission, setMission] = useState(null);
3 const [loading, setLoading] = useState(true);
4 const [error, setError] = useState(null);
5
6 useEffect(() => {
7 let isMounted = true; // Flaga do zapobiegania aktualizacji stanu po odmontowaniu
8
9 async function fetchMission() {
10 try {
11 setLoading(true);
12
13 const response = await fetch(
14 `https://api.spaceagency.com/missions/${missionId}`
15 );
16
17 if (!response.ok) {
18 throw new Error(`HTTP Error: ${response.status}`);
19 }
20
21 const data = await response.json();
22
23 // Aktualizuj stan tylko jeśli komponent jest nadal zamontowany
24 if (isMounted) {
25 setMission(data);
26 setError(null);
27 }
28 } catch (err) {
29 if (isMounted) {
30 setError(`Nie udało się pobrać danych misji: ${err.message}`);
31 setMission(null);
32 }
33 } finally {
34 if (isMounted) {
35 setLoading(false);
36 }
37 }
38 }
39
40 fetchMission();
41
42 // Funkcja czyszcząca
43 return () => {
44 isMounted = false; // Oznacz, że komponent został odmontowany
45 };
46 }, [missionId]); // Pobierz dane ponownie, gdy zmieni się missionId
47
48 // Renderowanie
49 if (loading) return <div>Ładowanie danych misji...</div>;
50 if (error) return <div>Błąd: {error}</div>;
51 if (!mission) return <div>Nie znaleziono misji</div>;
52
53 return (
54 <div className="mission-details">
55 <h2>{mission.name}</h2>
56 <p>Status: {mission.status}</p>
57 <p>Data startu: {new Date(mission.launchDate).toLocaleDateString()}</p>
58 <p>Dowódca: {mission.commander}</p>
59 {/* Więcej szczegółów misji */}
60 </div>
61 );
62}W tym przykładzie:
missionId, które może się zmieniaćisMounted do zapobiegania aktualizacji stanu po odmontowaniu komponentuisMounted = false przy odmontowaniuAPI Fetch nie ma wbudowanego mechanizmu anulowania, ale możemy użyć AbortController, aby przerwać żądanie:
1function SpaceWeather({ location }) {
2 const [weatherData, setWeatherData] = useState(null);
3 const [loading, setLoading] = useState(true);
4 const [error, setError] = useState(null);
5
6 useEffect(() => {
7 // Utwórz AbortController do anulowania żądania
8 const abortController = new AbortController();
9 const signal = abortController.signal;
10
11 async function fetchWeatherData() {
12 try {
13 setLoading(true);
14
15 const response = await fetch(
16 `https://api.spaceagency.com/weather/${location}`,
17 { signal } // Przypisanie sygnału do żądania
18 );
19
20 if (!response.ok) {
21 throw new Error(`HTTP Error: ${response.status}`);
22 }
23
24 const data = await response.json();
25 setWeatherData(data);
26 setError(null);
27 } catch (err) {
28 // Ignoruj błędy związane z anulowaniem
29 if (err.name === 'AbortError') {
30 console.log('Żądanie pogody zostało anulowane');
31 } else {
32 setError(`Nie udało się pobrać danych pogodowych: ${err.message}`);
33 setWeatherData(null);
34 }
35 } finally {
36 setLoading(false);
37 }
38 }
39
40 fetchWeatherData();
41
42 // Funkcja czyszcząca anuluje żądanie, jeśli komponent
43 // zostanie odmontowany przed zakończeniem pobierania
44 return () => {
45 abortController.abort();
46 };
47 }, [location]);
48
49 // Renderowanie
50 if (loading) return <div>Ładowanie danych pogodowych...</div>;
51 if (error) return <div>Błąd: {error}</div>;
52 if (!weatherData) return <div>Brak danych pogodowych</div>;
53
54 return (
55 <div className="space-weather">
56 <h2>Pogoda kosmiczna: {location}</h2>
57 <p>Promieniowanie: {weatherData.radiation} mSv</p>
58 <p>Aktywność słoneczna: {weatherData.solarActivity}</p>
59 <p>Temperatura: {weatherData.temperature}°C</p>
60 </div>
61 );
62}W tym przykładzie:
AbortController do tworzenia sygnału, który może przerwać żądanie FetchabortController.abort(), aby anulować trwające żądanieAbortError, który jest rzucany, gdy żądanie zostało anulowaneCzasami potrzebujemy pobrać dane z kilku źródeł jednocześnie:
1function MissionDashboard({ missionId }) {
2 const [missionData, setMissionData] = useState({
3 details: null,
4 crew: [],
5 telemetry: null
6 });
7 const [loading, setLoading] = useState(true);
8 const [error, setError] = useState(null);
9
10 useEffect(() => {
11 let isMounted = true;
12
13 async function fetchMissionData() {
14 try {
15 setLoading(true);
16
17 // Pobieranie danych równolegle za pomocą Promise.all
18 const [detailsResponse, crewResponse, telemetryResponse] = await Promise.all([
19 fetch(`https://api.spaceagency.com/missions/${missionId}`),
20 fetch(`https://api.spaceagency.com/missions/${missionId}/crew`),
21 fetch(`https://api.spaceagency.com/missions/${missionId}/telemetry`)
22 ]);
23
24 // Sprawdzenie odpowiedzi
25 if (!detailsResponse.ok || !crewResponse.ok || !telemetryResponse.ok) {
26 throw new Error('Jedno lub więcej żądań zakończyło się błędem');
27 }
28
29 // Parsowanie danych równolegle
30 const [details, crew, telemetry] = await Promise.all([
31 detailsResponse.json(),
32 crewResponse.json(),
33 telemetryResponse.json()
34 ]);
35
36 if (isMounted) {
37 setMissionData({ details, crew, telemetry });
38 setError(null);
39 }
40 } catch (err) {
41 if (isMounted) {
42 setError(`Nie udało się pobrać danych misji: ${err.message}`);
43 }
44 } finally {
45 if (isMounted) {
46 setLoading(false);
47 }
48 }
49 }
50
51 fetchMissionData();
52
53 return () => {
54 isMounted = false;
55 };
56 }, [missionId]);
57
58 // Renderowanie
59 if (loading) return <div>Ładowanie danych misji...</div>;
60 if (error) return <div>Błąd: {error}</div>;
61
62 const { details, crew, telemetry } = missionData;
63
64 return (
65 <div className="mission-dashboard">
66 <h1>{details.name} - Panel kontrolny</h1>
67
68 <section className="mission-details">
69 <h2>Szczegóły misji</h2>
70 <p>Status: {details.status}</p>
71 <p>Cel: {details.objective}</p>
72 </section>
73
74 <section className="crew-info">
75 <h2>Załoga</h2>
76 <ul>
77 {crew.map(member => (
78 <li key={member.id}>
79 {member.name} - {member.role}
80 </li>
81 ))}
82 </ul>
83 </section>
84
85 <section className="telemetry-data">
86 <h2>Telemetria</h2>
87 <p>Tlen: {telemetry.oxygen}%</p>
88 <p>Paliwo: {telemetry.fuel}%</p>
89 <p>Prędkość: {telemetry.velocity} km/h</p>
90 </section>
91 </div>
92 );
93}W tym przykładzie:
Promise.all() do równoległego wykonywania wielu żądańmissionDataAby poprawić czytelność i utrzymanie kodu, warto wyodrębnić logikę pobierania danych poza komponent:
1// api.js - Serwisy API
2const API_BASE_URL = 'https://api.spaceagency.com';
3
4export async function fetchAstronauts() {
5 const response = await fetch(`${API_BASE_URL}/astronauts`);
6 if (!response.ok) {
7 throw new Error(`HTTP Error: ${response.status}`);
8 }
9 return response.json();
10}
11
12export async function fetchMission(missionId) {
13 const response = await fetch(`${API_BASE_URL}/missions/${missionId}`);
14 if (!response.ok) {
15 throw new Error(`HTTP Error: ${response.status}`);
16 }
17 return response.json();
18}
19
20// Komponent z wyodrębnioną logiką API
21function AstronautsList() {
22 const [astronauts, setAstronauts] = useState([]);
23 const [loading, setLoading] = useState(true);
24 const [error, setError] = useState(null);
25
26 useEffect(() => {
27 let isMounted = true;
28
29 async function getAstronauts() {
30 try {
31 setLoading(true);
32 const data = await fetchAstronauts();
33
34 if (isMounted) {
35 setAstronauts(data);
36 setError(null);
37 }
38 } catch (err) {
39 if (isMounted) {
40 setError(`Nie udało się pobrać danych: ${err.message}`);
41 setAstronauts([]);
42 }
43 } finally {
44 if (isMounted) {
45 setLoading(false);
46 }
47 }
48 }
49
50 getAstronauts();
51
52 return () => {
53 isMounted = false;
54 };
55 }, []);
56
57 // Renderowanie
58 //
59}Dla powtarzalnych operacji pobierania danych, najlepszym rozwiązaniem jest stworzenie własnego hooka:
1// useFetch.js - Własny hook do pobierania danych
2function useFetch(url, options = {}) {
3 const [data, setData] = useState(null);
4 const [loading, setLoading] = useState(true);
5 const [error, setError] = useState(null);
6
7 useEffect(() => {
8 let isMounted = true;
9 const abortController = new AbortController();
10 const signal = abortController.signal;
11
12 async function fetchData() {
13 try {
14 setLoading(true);
15
16 const response = await fetch(url, {
17 ...options,
18 signal,
19 });
20
21 if (!response.ok) {
22 throw new Error(`HTTP Error: ${response.status}`);
23 }
24
25 const result = await response.json();
26
27 if (isMounted) {
28 setData(result);
29 setError(null);
30 }
31 } catch (err) {
32 if (err.name !== 'AbortError' && isMounted) {
33 setError(`Nie udało się pobrać danych: ${err.message}`);
34 setData(null);
35 }
36 } finally {
37 if (isMounted) {
38 setLoading(false);
39 }
40 }
41 }
42
43 fetchData();
44
45 return () => {
46 isMounted = false;
47 abortController.abort();
48 };
49 }, [url, JSON.stringify(options)]);
50
51 return { data, loading, error };
52}
53
54// Użycie własnego hooka
55function PlanetInfo({ planetId }) {
56 const { data: planet, loading, error } = useFetch(
57 `https://api.spaceagency.com/planets/${planetId}`
58 );
59
60 if (loading) return <div>Ładowanie...</div>;
61 if (error) return <div>Błąd: {error}</div>;
62 if (!planet) return <div>Nie znaleziono planety</div>;
63
64 return (
65 <div className="planet-card">
66 <h2>{planet.name}</h2>
67 <p>Typ: {planet.type}</p>
68 <p>Średnica: {planet.diameter} km</p>
69 <p>Odległość od Słońca: {planet.distanceFromSun} mln km</p>
70 </div>
71 );
72}1// useDataFetching.js
2function useDataFetching(fetchFn, dependencies = [], options = {}) {
3 const {
4 initialData = null,
5 cacheKey = null,
6 maxRetries = 3,
7 retryDelay = 1000,
8 } = options;
9
10 const [data, setData] = useState(initialData);
11 const [loading, setLoading] = useState(true);
12 const [error, setError] = useState(null);
13
14 // Użycie useRef do śledzenia prób
15 const retriesRef = useRef(0);
16
17 // Sprawdzenie cache przy pierwszym renderowaniu
18 useEffect(() => {
19 if (cacheKey) {
20 const cachedData = localStorage.getItem(cacheKey);
21 if (cachedData) {
22 try {
23 setData(JSON.parse(cachedData));
24 setLoading(false);
25 } catch (e) {
26 console.error('Błąd parsowania danych z cache:', e);
27 }
28 }
29 }
30 }, [cacheKey]);
31
32 // Główna logika pobierania danych
33 useEffect(() => {
34 let isMounted = true;
35 const abortController = new AbortController();
36
37 async function fetchData() {
38 try {
39 setLoading(true);
40
41 const result = await fetchFn(abortController.signal);
42
43 if (isMounted) {
44 setData(result);
45 setError(null);
46
47 // Zapisanie do cache, jeśli dostępne
48 if (cacheKey) {
49 localStorage.setItem(cacheKey, JSON.stringify(result));
50 }
51
52 // Zresetuj licznik prób
53 retriesRef.current = 0;
54 }
55 } catch (err) {
56 if (!isMounted) return;
57
58 if (err.name === 'AbortError') {
59 console.log('Żądanie anulowane');
60 return;
61 }
62
63 // Logika ponownych prób
64 if (retriesRef.current < maxRetries) {
65 console.log(`Próba ${retriesRef.current + 1} z ${maxRetries} nie powiodła się. Ponowienie za ${retryDelay}ms`);
66
67 setTimeout(() => {
68 if (isMounted) {
69 retriesRef.current += 1;
70 fetchData(); // Ponowna próba
71 }
72 }, retryDelay * Math.pow(2, retriesRef.current)); // Wykładniczy backoff
73
74 return;
75 }
76
77 // Po wyczerpaniu wszystkich prób
78 setError(`Nie udało się pobrać danych: ${err.message}`);
79 setData(initialData);
80 } finally {
81 if (isMounted) {
82 setLoading(false);
83 }
84 }
85 }
86
87 fetchData();
88
89 return () => {
90 isMounted = false;
91 abortController.abort();
92 };
93 }, [...dependencies]);
94
95 // Funkcja do ręcznego odświeżania danych
96 const refetch = useCallback(async () => {
97 setLoading(true);
98 retriesRef.current = 0;
99
100 try {
101 const abortController = new AbortController();
102 const result = await fetchFn(abortController.signal);
103 setData(result);
104 setError(null);
105
106 if (cacheKey) {
107 localStorage.setItem(cacheKey, JSON.stringify(result));
108 }
109 } catch (err) {
110 setError(`Nie udało się odświeżyć danych: ${err.message}`);
111 } finally {
112 setLoading(false);
113 }
114 }, [fetchFn, cacheKey]);
115
116 return { data, loading, error, refetch };
117}
118
119// Przykład użycia
120function MarsMissions() {
121 // Funkcja do pobrania danych
122 const fetchMissions = useCallback(async (signal) => {
123 const response = await fetch('https://api.spaceagency.com/mars/missions', { signal });
124 if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
125 return response.json();
126 }, []);
127
128 // Użycie zaawansowanego hooka
129 const {
130 data: missions,
131 loading,
132 error,
133 refetch
134 } = useDataFetching(fetchMissions, [], {
135 initialData: [],
136 cacheKey: 'mars-missions-cache',
137 maxRetries: 2
138 });
139
140 return (
141 <div className="mars-missions">
142 <div className="header">
143 <h2>Misje na Marsa</h2>
144 <button onClick={refetch} disabled={loading}>
145 {loading ? 'Odświeżanie...' : 'Odśwież'}
146 </button>
147 </div>
148
149 {loading && missions.length === 0 && <div>Ładowanie misji...</div>}
150 {error && <div className="error">Błąd: {error}</div>}
151
152 <ul className="missions-list">
153 {missions.map(mission => (
154 <li key={mission.id} className={`mission-item ${mission.status}`}>
155 <h3>{mission.name}</h3>
156 <p>Start: {new Date(mission.launchDate).toLocaleDateString()}</p>
157 <p>Status: {mission.status}</p>
158 </li>
159 ))}
160 </ul>
161
162 {missions.length === 0 && !loading && !error && (
163 <p>Nie znaleziono żadnych misji.</p>
164 )}
165 </div>
166 );
167}W tym zaawansowanym przykładzie:
useDataFetching z obsługą ponownych prób, pamięcią podręczną i funkcją ręcznego odświeżaniaPoza podstawową obsługą błędów w komponentach, warto zaimplementować bardziej zaawansowane techniki:
Granice błędów pozwalają na elegancką obsługę nieoczekiwanych błędów:
1// ErrorBoundary.js
2class ErrorBoundary extends React.Component {
3 constructor(props) {
4 super(props);
5 this.state = { hasError: false, error: null, errorInfo: null };
6 }
7
8 static getDerivedStateFromError(error) {
9 // Aktualizacja stanu, aby następny render pokazał UI awaryjne
10 return { hasError: true };
11 }
12
13 componentDidCatch(error, errorInfo) {
14 // Możesz też zalogować błąd do serwisu monitoringu
15 console.error('Przechwycono błąd:', error, errorInfo);
16 this.setState({ error, errorInfo });
17
18 // Wysyłanie błędu do serwisu raportowania
19 // logErrorToService(error, errorInfo);
20 }
21
22 render() {
23 if (this.state.hasError) {
24 // Możesz wyrenderować dowolny UI awaryjny
25 return (
26 <div className="error-boundary">
27 <h2>Coś poszło nie tak!</h2>
28 <p>Wystąpił błąd podczas renderowania komponentu.</p>
29 <details>
30 <summary>Szczegóły błędu</summary>
31 <p>{this.state.error && this.state.error.toString()}</p>
32 <p>Komponenty: {this.state.errorInfo && this.state.errorInfo.componentStack}</p>
33 </details>
34 <button onClick={() => this.setState({ hasError: false })}>
35 Spróbuj ponownie
36 </button>
37 </div>
38 );
39 }
40
41 return this.props.children;
42 }
43}
44
45// Użycie error boundary dla komponentu
46function App() {
47 return (
48 <div className="app">
49 <header>Centrum kontroli misji</header>
50
51 <ErrorBoundary>
52 <MissionDashboard missionId="mars-2030" />
53 </ErrorBoundary>
54
55 <ErrorBoundary>
56 <AstronautsList />
57 </ErrorBoundary>
58
59 <footer>Space Agency © 2023</footer>
60 </div>
61 );
62}Gdy API jest niestabilne, warto implementować strategie automatycznych ponownych prób:
1async function fetchWithRetry(url, options = {}, maxRetries = 3, delay = 1000) {
2 let retries = 0;
3
4 while (retries < maxRetries) {
5 try {
6 const response = await fetch(url, options);
7
8 if (!response.ok) {
9 throw new Error(`HTTP Error: ${response.status}`);
10 }
11
12 return await response.json();
13 } catch (err) {
14 retries++;
15
16 if (retries >= maxRetries) {
17 throw err; // Wszystkie próby nieudane, przekaż błąd dalej
18 }
19
20 console.log(`Próba ${retries} z ${maxRetries} nieudana. Ponowienie za ${delay}ms`);
21
22 // Odczekaj przed kolejną próbą (wykładniczy backoff)
23 await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, retries - 1)));
24 }
25 }
26}Warto również obsługiwać problemy z połączeniem internetowym:
1// Hook do monitorowania stanu połączenia
2function useOnlineStatus() {
3 const [isOnline, setIsOnline] = useState(navigator.onLine);
4
5 useEffect(() => {
6 function handleOnline() {
7 setIsOnline(true);
8 }
9
10 function handleOffline() {
11 setIsOnline(false);
12 }
13
14 window.addEventListener('online', handleOnline);
15 window.addEventListener('offline', handleOffline);
16
17 return () => {
18 window.removeEventListener('online', handleOnline);
19 window.removeEventListener('offline', handleOffline);
20 };
21 }, []);
22
23 return isOnline;
24}
25
26// Użycie w komponencie
27function DataFetchingComponent() {
28 const isOnline = useOnlineStatus();
29 const { data, loading, error, refetch } = useFetch('https://api.example.com/data');
30
31 if (!isOnline) {
32 return (
33 <div className="offline-message">
34 <h3>Brak połączenia z internetem</h3>
35 <p>Sprawdź połączenie i spróbuj ponownie.</p>
36 </div>
37 );
38 }
39
40 if (loading) return <div>Ładowanie...</div>;
41 if (error) return <div>Błąd: {error}</div>;
42
43 return (
44 <div>
45 <h2>Dane</h2>
46 {/* Renderowanie danych */}
47 </div>
48 );
49}Podczas opracowywania aplikacji, rzeczywiste API może być niedostępne lub niegotowe. W takim przypadku warto zasymulować API:
1// mockApi.js
2const DELAY = 1000; // Symulacja opóźnienia sieci
3
4export function mockFetch(data, shouldFail = false, failureRate = 0) {
5 return new Promise((resolve, reject) => {
6 setTimeout(() => {
7 // Symulacja losowego błędu
8 if (shouldFail || Math.random() < failureRate) {
9 reject(new Error('Symulowany błąd API'));
10 return;
11 }
12
13 resolve(data);
14 }, DELAY);
15 });
16}
17
18// Przykładowe dane
19const MOCK_ASTRONAUTS = [
20 { id: 1, name: 'Jan Kowalski', role: 'Dowódca' },
21 { id: 2, name: 'Anna Nowak', role: 'Pilot' },
22 { id: 3, name: 'Piotr Wiśniewski', role: 'Inżynier pokładowy' }
23];
24
25export function getAstronauts() {
26 return mockFetch(MOCK_ASTRONAUTS, false, 0.2);
27}MSW to biblioteka, która pozwala symulować żądania API na poziomie przeglądarki:
1// mockServiceWorker.js
2import { setupWorker, rest } from 'msw';
3
4// Dane do symulacji
5const astronauts = [
6 { id: 1, name: 'Jan Kowalski', role: 'Dowódca' },
7 { id: 2, name: 'Anna Nowak', role: 'Pilot' },
8 { id: 3, name: 'Piotr Wiśniewski', role: 'Inżynier pokładowy' }
9];
10
11const missions = [
12 {
13 id: 'mars-2030',
14 name: 'Mars 2030',
15 status: 'Planowana',
16 launchDate: '2030-05-15',
17 commander: 'Jan Kowalski'
18 },
19 {
20 id: 'iss-2023',
21 name: 'ISS Expedition 70',
22 status: 'W trakcie',
23 launchDate: '2023-09-01',
24 commander: 'Anna Nowak'
25 }
26];
27
28// Definiowanie handlerów
29export const handlers = [
30 // Pobieranie wszystkich astronautów
31 rest.get('https://api.spaceagency.com/astronauts', (req, res, ctx) => {
32 return res(
33 ctx.delay(1000),
34 ctx.status(200),
35 ctx.json(astronauts)
36 );
37 }),
38
39 // Pobieranie astronauty po ID
40 rest.get('https://api.spaceagency.com/astronauts/:id', (req, res, ctx) => {
41 const { id } = req.params;
42 const astronaut = astronauts.find(a => a.id === parseInt(id));
43
44 if (!astronaut) {
45 return res(
46 ctx.delay(500),
47 ctx.status(404),
48 ctx.json({ error: 'Astronauta nie znaleziony' })
49 );
50 }
51
52 return res(
53 ctx.delay(500),
54 ctx.status(200),
55 ctx.json(astronaut)
56 );
57 }),
58
59 // Pobieranie misji
60 rest.get('https://api.spaceagency.com/missions', (req, res, ctx) => {
61 return res(
62 ctx.delay(800),
63 ctx.status(200),
64 ctx.json(missions)
65 );
66 }),
67
68 // Pobieranie misji po ID
69 rest.get('https://api.spaceagency.com/missions/:id', (req, res, ctx) => {
70 const { id } = req.params;
71 const mission = missions.find(m => m.id === id);
72
73 if (!mission) {
74 return res(
75 ctx.delay(500),
76 ctx.status(404),
77 ctx.json({ error: 'Misja nie znaleziona' })
78 );
79 }
80
81 return res(
82 ctx.delay(700),
83 ctx.status(200),
84 ctx.json(mission)
85 );
86 })
87];
88
89// Inicjalizacja service workera
90export const worker = setupWorker(...handlers);Następnie w pliku wejściowym aplikacji (np.
index.js):1// index.js
2import React from 'react';
3import { createRoot } from 'react-dom/client';
4import App from './App';
5
6// Warunkowo inicjalizuj mock API tylko w środowisku deweloperskim
7if (process.env.NODE_ENV === 'development') {
8 const { worker } = require('./mockServiceWorker');
9 worker.start();
10}
11
12const root = createRoot(document.getElementById('root'));
13root.render(<App />);Pobieranie danych z API jest fundamentalną częścią większości aplikacji React. Klucz do sukcesu to:
Pamiętaj, że dobra obsługa pobierania danych znacząco wpływa na odczucia użytkownika. Nawet jeśli API jest wolne, odpowiednio zaprojektowany interfejs z informacjami o stanie ładowania, animacjami i obsługą błędów może sprawić, że aplikacja będzie wydawać się szybsza i bardziej niezawodna.