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

Forms in Real-World Use: a React Practical Example

Forms in Real-World Use

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

To wrap up Phase 3, let's finally connect ReviewForm to our real page – submitted reviews get collected and displayed right below it. Persistent storage (our own server + database) doesn't arrive until Phase 5 – for now, reviews live only in React state and disappear again after a page reload.

Updating App.jsx: wiring in ReviewForm

Add state for collected reviews and a callback function that ReviewForm calls on submit, in src/App.jsx:

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: 'boots-01', name: 'Hiking Boots', price: 89.99, imageUrl: 'https://picsum.photos/seed/1/300' },
  { sku: 'backpack-01', name: 'Trekking Backpack', price: 59.5, imageUrl: 'https://picsum.photos/seed/2/300' },
  { sku: 'jacket-01', name: 'Rain Jacket', 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(`Product Catalog (${SAMPLE_PRODUCTS.length} products)`);

  const filtered = SAMPLE_PRODUCTS.filter((product) =>
    product.name.toLowerCase().includes(query.toLowerCase())
  );

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

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

  return (
    <div className="app">
      <header className="app-header">
        <h1>Product Catalog</h1>
        {user ? (
          <p>Logged in as {user.username} <button onClick={logout}>Log out</button></p>
        ) : (
          <button onClick={() => login('Jane Doe')}>Log in</button>
        )}
      </header>
      <input
        ref={searchInputRef}
        type="text"
        placeholder="Search products..."
        value={query}
        onChange={(event) => setQuery(event.target.value)}
      />
      {mostExpensiveProduct && (
        <p>Most expensive product: {mostExpensiveProduct.name} (${mostExpensiveProduct.price.toFixed(2)})</p>
      )}
      <div className="product-grid">
        {filtered.map((product) => (
          <ProductCard
            key={product.sku}
            name={product.name}
            price={product.price}
            imageUrl={product.imageUrl}
            onSelect={() => alert(product.name + ' tapped')}
          />
        ))}
      </div>

      <section className="reviews-section">
        <h2>Review a product</h2>
        <ReviewForm onSubmitReview={handleReviewSubmit} />

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

export default App;

Project structure after this chapter

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

setReviews([...reviews, {{ ...review, id: Date.now() }}]) follows the same pattern as every state update in this tutorial: a NEW array instead of reviews.push(...). Date.now() as a simple, unique id is enough for our purposes – key={{review.id}} in the list relies on exactly this (see chapter 7).

Save, fill out the form, and submit: your review appears instantly below the form, without the page reloading. That wraps up Phase 3 (Forms) – next, we'll build several real sub-pages with routing.

Tipp: In Phase 5 (API integration, starting chapter 25) we'll build our own small server plus a MySQL database, so reviews survive a page reload – until then, local React state is entirely sufficient to understand and test the form itself.