Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Projekt Glowny: Kosmiczny Katalog Gwiazd

Czas polaczyc wszystko, czego sie nauczyłeś! W tym projekcie zbudujesz kompletna aplikację "Kosmiczny Katalog Gwiazd" (Star Catalog), która pobiera dane z API, obsługuje stany ładowania i błędów, umożliwia wyszukiwanie, i wspiera paginacje.

Wymagania projektu

Twoją aplikacja powinna zawierac:

  1. Pobieranie danych z API - uzyj
    fetch()
    z async/await
  2. Stan ładowania - wyswietl spinner lub skeleton podczas ładowania
  3. Obsługa błędów - pokaz komunikat błędu z opcja ponownej proby
  4. Stan pustych danych - obsluz sytuacje, gdy nie ma wynikow
  5. Wyszukiwanie - filtruj wyniki po nazwie z debouncing
  6. Paginacja - nawigacja między stronami wynikow
  7. Custom hook useFetch - wyodrebnij logikę pobierania danych

Architektura aplikacji

1// Struktura komponentów:
2// App
3// ├── SearchBar (input wyszukiwania)
4// ├── useFetch (custom hook)
5// ├── LoadingSpinner / ErrorMessage / EmptyState
6// ├── StarList (lista wynikow)
7// │   └── StarCard (pojedynczy wynik)
8// └── Pagination (nawigacja stron)

Implementacja

1import React, { useState, useEffect } from 'react';
2
3// Custom Hook - useFetch
4function useFetch(url) {
5  const [data, setData] = useState(null);
6  const [loading, setLoading] = useState(true);
7  const [error, setError] = useState(null);
8
9  useEffect(() => {
10    if (!url) return;
11    const controller = new AbortController();
12
13    async function fetchData() {
14      try {
15        setLoading(true);
16        setError(null);
17        const response = await fetch(url, {
18          signal: controller.signal
19        });
20        if (!response.ok) {
21          throw new Error(\`HTTP \${response.status}\`);
22        }
23        const result = await response.json();
24        setData(result);
25      } catch (err) {
26        if (err.name !== 'AbortError') {
27          setError(err.message);
28        }
29      } finally {
30        setLoading(false);
31      }
32    }
33
34    fetchData();
35    return () => controller.abort();
36  }, [url]);
37
38  const refetch = () => {
39    setData(null);
40    setLoading(true);
41    setError(null);
42    // Trigger re-fetch via state change
43  };
44
45  return { data, loading, error, refetch };
46}
47
48// Komponenty pomocnicze
49function LoadingSpinner() {
50  return (
51    <div style={{ textAlign: 'center', padding: '40px' }}>
52      <div className="spinner"></div>
53      <p>Skanowanie galaktyki...</p>
54    </div>
55  );
56}
57
58function ErrorMessage({ message, onRetry }) {
59  return (
60    <div className="error-panel">
61      <h3>Utracono lacznosc!</h3>
62      <p>{message}</p>
63      <button onClick={onRetry}>Ponow skanowanie</button>
64    </div>
65  );
66}
67
68function EmptyState({ query }) {
69  return (
70    <div className="empty-panel">
71      <p>Brak wynikow {query ? \`dla "\${query}"\` : ''}</p>
72      <p>Sprobuj innego zapytania</p>
73    </div>
74  );
75}
76
77function StarCard({ star }) {
78  return (
79    <div className="star-card">
80      <h3>{star.name}</h3>
81      <p>Wzrost: {star.height} cm</p>
82      <p>Waga: {star.mass} kg</p>
83      <p>Kolor wlosow: {star.hair_color}</p>
84      <p>Rok urodzenia: {star.birth_year}</p>
85    </div>
86  );
87}
88
89function Pagination({ page, totalPages, onPageChange }) {
90  return (
91    <div className="pagination">
92      <button
93        onClick={() => onPageChange(page - 1)}
94        disabled={page === 1}
95      >
96        Poprzednia
97      </button>
98      <span>Strona {page} z {totalPages}</span>
99      <button
100        onClick={() => onPageChange(page + 1)}
101        disabled={page >= totalPages}
102      >
103        Nastepna
104      </button>
105    </div>
106  );
107}
108
109// Glowna aplikacja
110function App() {
111  const [searchQuery, setSearchQuery] = useState('');
112  const [debouncedQuery, setDebouncedQuery] = useState('');
113  const [page, setPage] = useState(1);
114
115  // Debounce wyszukiwania
116  useEffect(() => {
117    const timer = setTimeout(() => {
118      setDebouncedQuery(searchQuery);
119      setPage(1); // Reset strony przy nowym wyszukiwaniu
120    }, 500);
121    return () => clearTimeout(timer);
122  }, [searchQuery]);
123
124  // Buduj URL z parametrami
125  const url = debouncedQuery
126    ? \`https://swapi.dev/api/people/?search=\${debouncedQuery}&page=\${page}\`
127    : \`https://swapi.dev/api/people/?page=\${page}\`;
128
129  const { data, loading, error } = useFetch(url);
130  const totalPages = data ? Math.ceil(data.count / 10) : 1;
131
132  return (
133    <div className="app">
134      <h1>Kosmiczny Katalog Gwiazd</h1>
135
136      <input
137        type="text"
138        value={searchQuery}
139        onChange={e => setSearchQuery(e.target.value)}
140        placeholder="Szukaj w galaktyce..."
141        className="search-input"
142      />
143
144      {loading && <LoadingSpinner />}
145
146      {error && (
147        <ErrorMessage
148          message={error}
149          onRetry={() => setPage(p => p)}
150        />
151      )}
152
153      {!loading && !error && data && (
154        <>
155          {data.results.length === 0 ? (
156            <EmptyState query={debouncedQuery} />
157          ) : (
158            <>
159              <p>Znaleziono: {data.count} wynikow</p>
160              <div className="star-grid">
161                {data.results.map(star => (
162                  <StarCard key={star.name} star={star} />
163                ))}
164              </div>
165              <Pagination
166                page={page}
167                totalPages={totalPages}
168                onPageChange={setPage}
169              />
170            </>
171          )}
172        </>
173      )}
174    </div>
175  );
176}
177
178export default App;

Ten projekt laczy wszystkie techniki z tego modułu: fetch API, loading/error/empty states, useEffect, custom hook useFetch, debouncing, i paginacje. Sprobuj zbudowac go samodzielnie, a następnie porownaj z powyższym rozwiązaniem!

Przejdź do CodeWorlds