useEffect und API-Aufrufe mit React Bootstrap
In diesem Schritt lernen wir den useEffect-Hook kennen und implementieren unsere ersten API-Aufrufe, um echte Buchdaten zu laden. Wir werden ausschliesslich React Bootstrap für die Gestaltung verwenden.
Ziel dieses Schritts
Abschnitt betitelt „Ziel dieses Schritts“- Den useEffect-Hook verstehen
- API-Aufrufe implementieren
- Lade- und Fehlerzustände verwalten
- Eine einfache Bücher-API verwenden
Was ist useEffect?
Abschnitt betitelt „Was ist useEffect?“Der useEffect-Hook ermöglicht uns, Seiteneffekte in funktionalen Komponenten auszuführen. Seiteneffekte sind alles, was ausserhalb des normalen Rendering-Prozesses stattfindet, wie:
- Daten von APIs laden
- Event-Listener einrichten
- Timers starten/stoppen
- Änderungen am DOM vornehmen
Eine einfache Book API Service erstellen
Abschnitt betitelt „Eine einfache Book API Service erstellen“Bevor wir die Google Books API in einem späteren Schritt integrieren, werden wir zunächst einen eigenen Service erstellen, der simulierte API-Aufrufe durchführt.
Erstelle eine neue Datei src/services/bookService.js:
// Eine Sammlung von Beispiel-Büchern für unsere simulierte APIconst sampleBooks = [ { id: "1", title: "Der Herr der Ringe: Die Gefährten", author: "J.R.R. Tolkien", description: "Der erste Teil der epischen Fantasy-Trilogie, in der Frodo Beutlin eine gefährliche Reise unternimmt, um den Einen Ring zu zerstören.", imageUrl: "https://images.unsplash.com/photo-1507842217343-583bb7270b66?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80", }, { id: "2", title: "Harry Potter und der Stein der Weisen", author: "J.K. Rowling", description: "Das erste Abenteuer des jungen Zauberers Harry Potter und seiner Freunde an der Hogwarts-Schule für Hexerei und Zauberei.", imageUrl: "https://images.unsplash.com/photo-1544947950-fa07a98d237f?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=80", }, // Weitere Bücher...];
// Simuliert einen asynchronen API-Aufruf mit einer kurzen Verzögerungconst delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Holt alle Bücher mit einer simulierten Netzwerkverzögerungexport const getAllBooks = async () => { await delay(800); // 800ms Verzögerung simuliert Netzwerklatenz return [...sampleBooks];};
// Sucht Bücher basierend auf einem Suchbegriffexport const searchBooks = async (searchTerm) => { await delay(800); if (!searchTerm.trim()) return [...sampleBooks];
// Filtern der Bücher basierend auf dem Suchbegriff (Titel oder Autor) return sampleBooks.filter( (book) => book.title.toLowerCase().includes(searchTerm.toLowerCase()) || book.author.toLowerCase().includes(searchTerm.toLowerCase()) );};
// Holt ein einzelnes Buch anhand seiner IDexport const getBookById = async (id) => { await delay(500); const book = sampleBooks.find((book) => book.id === id);
if (!book) { throw new Error("Buch nicht gefunden"); }
return book;};BookCard-Komponente aktualisieren
Abschnitt betitelt „BookCard-Komponente aktualisieren“Aktualisiere die BookCard-Komponente, um React Bootstrap zu verwenden:
import { useState } from "react";// GEÄNDERT: Import der React Bootstrap Komponentenimport { Card, Button } from "react-bootstrap";import { FaHeart, FaRegHeart, FaInfoCircle } from "react-icons/fa";
// GEÄNDERT: Änderung der Prop-Strukturfunction BookCard({ book }) { // GEÄNDERT: Destructuring der Buchdaten const { title, author, imageUrl, description } = book;
// State für den Favoriten-Status const [isFavorite, setIsFavorite] = useState(false);
// Funktion zum Umschalten des Favoriten-Status const toggleFavorite = () => { setIsFavorite(!isFavorite); };
// Standard-Bild, falls kein Bild-URL bereitgestellt wird const defaultImage = "https://placehold.co/128x192";
// GEÄNDERT: Verwendung von React Bootstrap Card-Komponente return ( <Card className="mb-3"> <div className="d-flex"> <div style={{ flex: "0 0 150px" }}> <Card.Img src={imageUrl || defaultImage} alt={`Cover von ${title}`} style={{ height: "200px", objectFit: "cover" }} /> </div> <Card.Body> <Card.Title>{title}</Card.Title> <Card.Subtitle className="mb-2 text-muted">{author}</Card.Subtitle> <Card.Text> {description ? description.length > 100 ? `${description.substring(0, 100)}...` : description : "Keine Beschreibung verfügbar"} </Card.Text> <div className="d-flex gap-2"> <Button variant="primary"> <FaInfoCircle className="me-1" /> Details </Button> <Button variant={isFavorite ? "danger" : "outline-danger"} onClick={toggleFavorite} > {isFavorite ? <FaHeart /> : <FaRegHeart />} </Button> </div> </Card.Body> </div> </Card> );}
export default BookCard;LoadingSpinner Komponente erstellen
Abschnitt betitelt „LoadingSpinner Komponente erstellen“Erstellen wir eine wiederverwendbare Ladeanzeige für unsere App mit React Bootstrap:
mkdir -p src/components/LoadingSpinner// GEÄNDERT: Verwende React Bootstrap für den Spinnerimport { Spinner } from "react-bootstrap";
function LoadingSpinner({ message = "Laden..." }) { return ( <div className="text-center my-5"> <Spinner animation="border" role="status" variant="primary" /> <p className="mt-3">{message}</p> </div> );}
export default LoadingSpinner;BookList-Komponente aktualisieren
Abschnitt betitelt „BookList-Komponente aktualisieren“Aktualisiere src/components/BookList/BookList.jsx, um React Bootstrap zu verwenden:
import { Row, Col, Alert } from "react-bootstrap";import BookCard from "../BookCard/BookCard";import LoadingSpinner from "../LoadingSpinner/LoadingSpinner";
// ÄNDERUNG: Komplett überarbeitet mit React Bootstrap Grid-Systemfunction BookList({ books, loading }) { // Wenn Bücher geladen werden, zeige einen Ladezustand an if (loading) { return <LoadingSpinner message="Bücher werden geladen..." />; }
// Wenn keine Bücher vorhanden sind, zeige eine Nachricht an if (!books || books.length === 0) { return <Alert variant="secondary">Keine Bücher gefunden.</Alert>; }
return ( <Row xs={1} md={1} lg={1} className="g-4"> {books.map((book) => ( <Col key={book.id}> <BookCard book={book} /> </Col> ))} </Row> );}
export default BookList;App.jsx mit useEffect aktualisieren
Abschnitt betitelt „App.jsx mit useEffect aktualisieren“Jetzt aktualisieren wir die App.jsx-Datei, um useEffect zu verwenden und Bücher über unseren Service zu laden:
// GEÄNDERT: useEffect importierenimport { useState, useEffect } from "react";import Header from "./components/Header/Header";import BookList from "./components/BookList/BookList";import SearchBar from "./components/SearchBar/SearchBar";// GEÄNDERT: Import der API-Funktionenimport { getAllBooks, searchBooks } from "./services/bookService";// GEÄNDERT: Import der React Bootstrap Komponentenimport { Container, Alert } from "react-bootstrap";
function App() { // State für die angezeigten Bücher const [books, setBooks] = useState([]); // State für den Ladezustand const [loading, setLoading] = useState(true); // State für Fehler const [error, setError] = useState(null); // State für den Suchbegriff const [searchTerm, setSearchTerm] = useState("");
// HINZUGEFÜGT: Effekt zum Laden aller Bücher beim ersten Rendervorgang useEffect(() => { async function loadInitialBooks() { try { setLoading(true); const data = await getAllBooks(); setBooks(data); setError(null); } catch (err) { setError( "Fehler beim Laden der Bücher. Bitte versuche es später erneut." ); console.error("Error loading books:", err); } finally { setLoading(false); } }
loadInitialBooks(); }, []); // Leeres Abhängigkeitsarray bedeutet: nur einmal beim Mounten ausführen
// GEÄNDERT: Funktion zum Suchen von Büchern const handleSearch = async (term) => { setSearchTerm(term);
try { setLoading(true); const results = await searchBooks(term); setBooks(results); setError(null); } catch (err) { setError("Fehler bei der Suche. Bitte versuche es später erneut."); console.error("Error searching books:", err); } finally { setLoading(false); } };
// GEÄNDERT: Verwende React Bootstrap Container und Komponenten return ( <div className="App"> <Header /> <Container className="py-4"> <h2>Willkommen im Bücher-Projekt</h2> <p className="lead"> Entdecke neue Bücher und verwalte deine persönliche Büchersammlung. Hier finden Sie Informationen zu verschiedenen Büchern. </p>
<SearchBar onSearch={handleSearch} />
{error && <Alert variant="danger">{error}</Alert>}
{searchTerm && !loading && !error && ( <Alert variant="info"> Ergebnisse für: <strong>{searchTerm}</strong>({books.length}{" "} {books.length === 1 ? "Buch" : "Bücher"} gefunden) </Alert> )}
<h3>{searchTerm ? "Suchergebnisse" : "Alle Bücher"}</h3> <BookList books={books} loading={loading} /> </Container> </div> );}
export default App;Testen der API-Integration mit useEffect
Abschnitt betitelt „Testen der API-Integration mit useEffect“Starte deine Anwendung:
npm run devJetzt sollten Sie folgendes sehen und testen können:
- Ein Ladeindikator wird beim Start der App angezeigt
- Bücher werden automatisch geladen, wenn die App startet
- Die Suchfunktion lädt Bücher basierend auf dem Suchbegriff
- Fehlermeldungen werden angezeigt, wenn etwas schief geht
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“- Wir haben den
useEffect-Hook kennengelernt - Wir haben einen einfachen API-Service implementiert
- Wir haben React Bootstrap für alle UI-Komponenten verwendet, ohne eigenes CSS
- Wir haben Ladezustände und Fehlerbehandlung hinzugefügt
- Wir haben gelernt, wie man asynchrone Aktionen in React ausführt
Die wichtigsten Änderungen:
- Hinzufügen des
useEffect-Hooks zum Laden der Daten beim ersten Render - Umstellung aller UI-Komponenten auf React Bootstrap
- Implementierung von asynchronen API-Aufrufen
Im nächsten Schritt werden wir die Google Books API integrieren, um echte Bücher zu suchen und anzuzeigen.