Produktformular und CRUD-Operationen
In diesem Kapitel erstellen wir ein Formular zur Erstellung und Bearbeitung von Produkten und implementieren die vollständigen CRUD-Operationen (Create, Read, Update, Delete). Nach Abschluss dieses Kapitels können Sie Produkte erstellen, bearbeiten und löschen.
Schritt 1: Erstellen der ProductForm Komponente
Abschnitt betitelt „Schritt 1: Erstellen der ProductForm Komponente“Erstellen Sie eine neue Datei ProductForm.jsx im Verzeichnis src/components:
import { useState, useEffect } from 'react';import { Form, Button, Row, Col } from 'react-bootstrap';
const ProductForm = ({ onSubmit, initialData, onCancel }) => { const [formData, setFormData] = useState({ title: '', description: '', price: 0, active: true });
const [errors, setErrors] = useState({}); const [validated, setValidated] = useState(false);
useEffect(() => { if (initialData) { setFormData(initialData); } }, [initialData]);
const handleChange = (e) => { const { name, value, type, checked } = e.target;
// Aktualisiere die Formulardaten setFormData(prevData => ({ ...prevData, [name]: type === 'checkbox' ? checked : value }));
// Validiere das Feld bei Änderungen validateField(name, type === 'checkbox' ? checked : value); };
const validateField = (name, value) => { let newErrors = { ...errors };
// Validierung für den Titel if (name === 'title') { if (!value || value.trim().length < 3) { newErrors.title = 'Der Titel muss mindestens 3 Zeichen lang sein'; } else { delete newErrors.title; } }
// Validierung für die Beschreibung if (name === 'description') { if (!value || value.trim().length < 3) { newErrors.description = 'Die Beschreibung muss mindestens 3 Zeichen lang sein'; } else if (value.trim().length > 200) { newErrors.description = 'Die Beschreibung darf maximal 200 Zeichen lang sein'; } else { delete newErrors.description; } }
setErrors(newErrors); };
const validateForm = () => { let isValid = true; const newErrors = {};
// Validiere den Titel if (!formData.title || formData.title.trim().length < 3) { newErrors.title = 'Der Titel muss mindestens 3 Zeichen lang sein'; isValid = false; }
// Validiere die Beschreibung if (!formData.description || formData.description.trim().length < 3) { newErrors.description = 'Die Beschreibung muss mindestens 3 Zeichen lang sein'; isValid = false; } else if (formData.description.trim().length > 200) { newErrors.description = 'Die Beschreibung darf maximal 200 Zeichen lang sein'; isValid = false; }
setErrors(newErrors); return isValid; };
const handleSubmit = (e) => { e.preventDefault();
// Setze validated auf true, um Bootstrap-Validierungsstile anzuzeigen setValidated(true);
// Überprüfe die Validierung if (!validateForm()) { return; // Stoppe die Übermittlung, wenn die Validierung fehlschlägt }
// Stelle sicher, dass price eine Zahl ist const submissionData = { ...formData, price: parseFloat(formData.price) };
// Entferne MongoDB-spezifische Felder const { _id, __v, ...cleanData } = submissionData;
onSubmit(cleanData);
if (!initialData) { // Reset form if it's a new product setFormData({ title: '', description: '', price: 0, active: true }); setValidated(false); } };
// Berechne die aktuelle Zeichenanzahl für die Beschreibung const descriptionLength = formData.description ? formData.description.length : 0;
return ( <Form noValidate validated={validated} onSubmit={handleSubmit}> <Form.Group className="mb-3" controlId="productTitle"> <Form.Label>Titel</Form.Label> <Form.Control type="text" name="title" value={formData.title} onChange={handleChange} isInvalid={!!errors.title} required minLength={3} /> <Form.Control.Feedback type="invalid"> {errors.title || 'Bitte geben Sie einen gültigen Titel ein (mindestens 3 Zeichen).'} </Form.Control.Feedback> </Form.Group>
<Form.Group className="mb-3" controlId="productDescription"> <Form.Label>Beschreibung</Form.Label> <Form.Control as="textarea" rows={3} name="description" value={formData.description} onChange={handleChange} isInvalid={!!errors.description} required minLength={3} maxLength={200} /> <div className="d-flex justify-content-between mt-1"> <Form.Control.Feedback type="invalid"> {errors.description || 'Bitte geben Sie eine gültige Beschreibung ein (3-200 Zeichen).'} </Form.Control.Feedback> <small className={descriptionLength > 200 ? 'text-danger' : 'text-muted'}> {descriptionLength}/200 Zeichen </small> </div> </Form.Group>
<Form.Group className="mb-3" controlId="productPrice"> <Form.Label>Preis (CHF)</Form.Label> <Form.Control type="number" step="0.01" name="price" value={formData.price} onChange={handleChange} required min={0} /> <Form.Control.Feedback type="invalid"> Bitte geben Sie einen gültigen Preis ein. </Form.Control.Feedback> </Form.Group>
<Form.Group className="mb-4" controlId="productActive"> <Form.Check type="checkbox" label="Aktiv" name="active" checked={formData.active} onChange={handleChange} /> </Form.Group>
<Row> <Col className="d-flex justify-content-end gap-2"> {onCancel && ( <Button variant="secondary" onClick={onCancel}> Abbrechen </Button> )} <Button variant="primary" type="submit"> {initialData ? 'Aktualisieren' : 'Erstellen'} </Button> </Col> </Row> </Form> );};
export default ProductForm;Schritt 2: Aktualisieren der Products-Seite für CRUD-Operationen
Abschnitt betitelt „Schritt 2: Aktualisieren der Products-Seite für CRUD-Operationen“Aktualisieren Sie die Datei src/pages/Products.jsx, um unsere neue ProductForm-Komponente zu verwenden und CRUD-Operationen zu ermöglichen:
import { useState, useEffect } from 'react';import { Container, Button, Alert, Spinner, Table, Modal } from 'react-bootstrap';import ProductForm from '../components/ProductForm.jsx';import api from '../services/productApi.js';
const Products = () => { const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedProduct, setSelectedProduct] = useState(null); const [showCreateModal, setShowCreateModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false);
useEffect(() => { fetchProducts(); }, []);
const fetchProducts = async () => { try { setLoading(true); const data = await api.getProducts(); setProducts(data); setError(null); } catch (err) { setError('Fehler beim Laden der Produkte. Bitte versuchen Sie es später erneut.'); console.error(err); } finally { setLoading(false); } };
const handleCreateProduct = async (productData) => { try { await api.createProduct(productData); fetchProducts(); // Aktualisiere die Liste nach dem Erstellen handleCloseCreateModal(); } catch (err) { setError('Fehler beim Erstellen des Produkts: ' + err.message); console.error(err); } };
const handleUpdateProduct = async (id, productData) => { try { await api.updateProduct(id, productData); fetchProducts(); // Aktualisiere die Liste nach dem Aktualisieren handleCloseEditModal(); } catch (err) { setError('Fehler beim Aktualisieren des Produkts: ' + err.message); console.error(err); } };
const handleDeleteProduct = async (id) => { if (window.confirm('Sind Sie sicher, dass Sie dieses Produkt löschen möchten?')) { try { await api.deleteProduct(id); fetchProducts(); // Aktualisiere die Liste nach dem Löschen
// Falls das gelöschte Produkt aktuell bearbeitet wird if (selectedProduct && selectedProduct._id === id) { handleCloseEditModal(); } } catch (err) { setError('Fehler beim Löschen des Produkts: ' + err.message); console.error(err); } } };
const handleShowCreateModal = () => setShowCreateModal(true); const handleCloseCreateModal = () => setShowCreateModal(false);
const handleShowEditModal = (product) => { setSelectedProduct(product); setShowEditModal(true); };
const handleCloseEditModal = () => { setShowEditModal(false); setSelectedProduct(null); };
return ( <Container className="py-4"> <h1 className="mb-4">Produkte</h1>
<Button variant="success" className="mb-4" onClick={handleShowCreateModal} > Neues Produkt erstellen </Button>
{error && <Alert variant="danger">{error}</Alert>}
<h2 className="mb-3">Produktliste</h2>
{loading ? ( <div className="text-center my-5"> <Spinner animation="border" role="status"> <span className="visually-hidden">Lade Produkte...</span> </Spinner> </div> ) : products.length > 0 ? ( <Table striped bordered hover responsive> <thead> <tr> <th>Titel</th> <th width="110">Preis</th> <th>Status</th> <th width="180">Aktionen</th> </tr> </thead> <tbody> {products.map(product => ( <tr key={product._id} onClick={() => handleShowEditModal(product)} style={{cursor: 'pointer'}}> <td><strong>{product.title}</strong><br/>{product.description}</td> <td>{parseFloat(product.price).toFixed(2)} CHF</td> <td> {product.active ? <span className="text-success">Aktiv</span> : <span className="text-secondary">Inaktiv</span> } </td> <td> <Button variant="primary" size="sm" className="me-2" onClick={(e) => { e.stopPropagation(); handleShowEditModal(product); }} > Bearbeiten </Button> <Button variant="danger" size="sm" onClick={(e) => { e.stopPropagation(); handleDeleteProduct(product._id); }} > Löschen </Button> </td> </tr> ))} </tbody> </Table> ) : ( <Alert variant="info">Keine Produkte gefunden.</Alert> )}
{/* Modal für Produkt-Erstellung */} <Modal show={showCreateModal} onHide={handleCloseCreateModal}> <Modal.Header closeButton> <Modal.Title>Neues Produkt erstellen</Modal.Title> </Modal.Header> <Modal.Body> <ProductForm onSubmit={handleCreateProduct} onCancel={handleCloseCreateModal} /> </Modal.Body> </Modal>
{/* Modal für Produkt-Bearbeitung */} <Modal show={showEditModal} onHide={handleCloseEditModal}> <Modal.Header closeButton> <Modal.Title>Produkt bearbeiten</Modal.Title> </Modal.Header> <Modal.Body> {selectedProduct && ( <ProductForm initialData={selectedProduct} onSubmit={(data) => handleUpdateProduct(selectedProduct._id, data)} onCancel={handleCloseEditModal} /> )} </Modal.Body> </Modal> </Container> );};
export default Products;Schritt 3: Testen der CRUD-Funktionalität
Abschnitt betitelt „Schritt 3: Testen der CRUD-Funktionalität“Starten Sie die Anwendung mit:
npm run devWenn Sie einen laufenden Backend-Server haben, können Sie jetzt:
- Produkte anzeigen: Auf der Produkte-Seite sollten alle vorhandenen Produkte angezeigt werden
- Produkt erstellen: Klicken Sie auf “Neues Produkt erstellen” und füllen Sie das Formular aus
- Produkt bearbeiten: Klicken Sie auf ein vorhandenes Produkt oder den “Bearbeiten”-Button
- Produkt löschen: Klicken Sie auf den “Löschen”-Button neben einem Produkt
Wenn Sie keinen Backend-Server haben, können Sie das Verhalten der App testen, indem Sie Mocking implementieren:
// Mock-Implementierung für testslet mockProducts = [ { _id: '1', title: 'Laptop XPS 15', description: 'Leistungsstarker Laptop für Profis', price: 1599.99, active: true }, // weitere Beispielprodukte...];
// Ersetzen Sie die api-Aufrufe in der Products-Komponente mit diesen Funktionen:const mockApi = { getProducts: async () => { return [...mockProducts]; }, createProduct: async (product) => { const newProduct = { ...product, _id: Date.now().toString() // Zufällige ID generieren }; mockProducts.push(newProduct); return newProduct; }, updateProduct: async (id, product) => { mockProducts = mockProducts.map(p => p._id === id ? { ...product, _id: id } : p ); return { ...product, _id: id }; }, deleteProduct: async (id) => { mockProducts = mockProducts.filter(p => p._id !== id); return { success: true }; }};Was passiert hier?
Abschnitt betitelt „Was passiert hier?“- Produktformular: Wir haben ein wiederverwendbares Formular erstellt, das sowohl für das Erstellen als auch für das Bearbeiten von Produkten verwendet werden kann
- Formularvalidierung: Wir führen Validierungen durch, um sicherzustellen, dass keine ungültigen Daten gesendet werden
- Modals: Wir verwenden Modals, um das Formular anzuzeigen, ohne die Hauptseite zu verlassen
- CRUD-Operationen: Wir haben alle vier grundlegenden Datenbankoperationen implementiert
- UX-Verbesserungen: Ladeanzeigen, Erfolgsmeldungen und Fehlerbehandlung verbessern die Benutzererfahrung
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“In diesem Abschnitt haben wir:
- Eine wiederverwendbare Formularkomponente für Produkte erstellt
- Validierungslogik für das Formular implementiert
- Modals für die Erstellung und Bearbeitung von Produkten eingerichtet
- CRUD-Operationen (Create, Read, Update, Delete) für Produkte implementiert
Unsere Anwendung ist jetzt in der Lage, Produkte zu erstellen, anzuzeigen, zu bearbeiten und zu löschen. Im nächsten Kapitel werden wir die Authentifizierung einrichten, damit nur autorisierte Benutzer auf die Anwendung zugreifen können.