Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Formulare im echten Einsatz: React-Praxisbeispiel

Formulare im echten Einsatz

~10 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026

Zum Abschluss von Phase 3 verbinden wir ReviewForm endlich mit unserer echten Seite – abgeschickte Bewertungen werden gesammelt und direkt darunter angezeigt. Eine dauerhafte Speicherung (unser eigener Server + Datenbank) folgt erst in Phase 5 – für den Moment leben die Bewertungen nur im React-State und sind nach einem Neuladen der Seite wieder weg.

App.jsx aktualisieren: ReviewForm einbinden

Ergänzen Sie in src/App.jsx einen State für gesammelte Bewertungen und eine Callback-Funktion, die ReviewForm beim Absenden aufruft:

src/App.jsx
import { useMemo, useRef, useState } from 'react';
import ProductCard from './components/ProductCard';
import ReviewForm from './components/ReviewForm';
import { useAuth } from './context/AuthContext';
import { useDocumentTitle } from './hooks/useDocumentTitle';

const SAMPLE_PRODUCTS = [
  { sku: 'schuhe-01', name: 'Wanderschuhe', price: 89.99, imageUrl: 'https://picsum.photos/seed/1/300' },
  { sku: 'rucksack-01', name: 'Trekking-Rucksack', price: 59.5, imageUrl: 'https://picsum.photos/seed/2/300' },
  { sku: 'jacke-01', name: 'Regenjacke', price: 74.0, imageUrl: 'https://picsum.photos/seed/3/300' },
];

function App() {
  const { user, login, logout } = useAuth();
  const [query, setQuery] = useState('');
  const [reviews, setReviews] = useState([]);
  const searchInputRef = useRef(null);

  useDocumentTitle(`Produktkatalog (${SAMPLE_PRODUCTS.length} Produkte)`);

  const gefiltert = SAMPLE_PRODUCTS.filter((produkt) =>
    produkt.name.toLowerCase().includes(query.toLowerCase())
  );

  const teuerstesProdukt = useMemo(() => {
    return gefiltert.reduce(
      (max, p) => (p.price > (max?.price ?? 0) ? p : max),
      null
    );
  }, [gefiltert]);

  function handleReviewSubmit(review) {
    setReviews([...reviews, { ...review, id: Date.now() }]);
  }

  return (
    <div className="app">
      <header className="app-header">
        <h1>Produktkatalog</h1>
        {user ? (
          <p>Angemeldet als {user.username} <button onClick={logout}>Abmelden</button></p>
        ) : (
          <button onClick={() => login('Max Mustermann')}>Anmelden</button>
        )}
      </header>
      <input
        ref={searchInputRef}
        type="text"
        placeholder="Produkt suchen..."
        value={query}
        onChange={(event) => setQuery(event.target.value)}
      />
      {teuerstesProdukt && (
        <p>Teuerstes Produkt: {teuerstesProdukt.name} ({teuerstesProdukt.price.toFixed(2)} €)</p>
      )}
      <div className="product-grid">
        {gefiltert.map((produkt) => (
          <ProductCard
            key={produkt.sku}
            name={produkt.name}
            price={produkt.price}
            imageUrl={produkt.imageUrl}
            onSelect={() => alert(produkt.name + ' angeklickt')}
          />
        ))}
      </div>

      <section className="reviews-section">
        <h2>Produkt bewerten</h2>
        <ReviewForm onSubmitReview={handleReviewSubmit} />

        {reviews.length === 0 ? (
          <p>Noch keine Bewertungen.</p>
        ) : (
          <ul className="review-list">
            {reviews.map((review) => (
              <li key={review.id}>
                <strong>{review.name}</strong> – {review.rating} Sterne
                <p>{review.comment}</p>
              </li>
            ))}
          </ul>
        )}
      </section>
    </div>
  );
}

export default App;

Projektstruktur nach diesem Kapitel

produktkatalog-web/
├── index.html
├── package.json
├── vite.config.js
└── src/
    ├── main.jsx
    ├── App.jsx                      (aktualisiert: ReviewForm eingebunden)
    ├── index.css
    ├── context/
    │   └── AuthContext.jsx
    ├── hooks/
    │   └── useDocumentTitle.js
    └── components/
        ├── ProductCard.jsx
        └── ReviewForm.jsx           ← NEU eingebunden

setReviews([...reviews, {{ ...review, id: Date.now() }}]) folgt demselben Muster wie bei jedem State-Update in diesem Tutorial: ein NEUES Array statt reviews.push(...). Date.now() als simple, eindeutige id reicht für unseren Zweck – key={{review.id}} in der Liste greift genau darauf zurück (siehe Kapitel 7).

Speichern Sie, füllen Sie das Formular aus und senden Sie es ab: Ihre Bewertung erscheint sofort unter dem Formular, ohne dass die Seite neu lädt. Damit ist Phase 3 (Forms) abgeschlossen – als Nächstes bauen wir mehrere echte Unterseiten mit Routing.

Tipp: In Phase 5 (API-Integration, ab Kapitel 25) bauen wir einen eigenen kleinen Server samt MySQL-Datenbank, damit Bewertungen einen Neuladen der Seite überleben – bis dahin ist der lokale React-State völlig ausreichend, um das Formular selbst zu verstehen und zu testen.