Formular-Validierung in React (ohne Bibliothek)
Form Validation in React
~12 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Jetzt bauen wir unser erstes echtes Formular: eine Produktbewertung mit Name, Sternebewertung und Kommentar. In diesem Kapitel validieren wir "von Hand", mit reinem React-State – im nächsten Kapitel lernen Sie dann eine Bibliothek kennen, die vieles davon automatisiert.
Das Prinzip: Fehler als eigener State
Neben dem State für die Formularwerte selbst führen wir einen ZWEITEN State-Wert für Validierungsfehler – meist ein Objekt, dessen Schlüssel den Feldnamen entsprechen:
const [values, setValues] = useState({ name: '', comment: '' });
const [errors, setErrors] = useState({});
function validate(values) {
const newErrors = {};
if (values.name.trim() === '') {
newErrors.name = 'Bitte geben Sie Ihren Namen ein.';
}
if (values.comment.trim().length < 10) {
newErrors.comment = 'Der Kommentar muss mindestens 10 Zeichen lang sein.';
}
return newErrors;
}components/ReviewForm.jsx anlegen
Legen Sie src/components/ReviewForm.jsx an – ein vollständiges, manuell validiertes Formular mit Name, Sternebewertung (1–5) und Kommentar:
import { useState } from 'react';
const ANFANGSWERTE = { name: '', rating: 5, comment: '' };
function validate(values) {
const errors = {};
if (values.name.trim() === '') {
errors.name = 'Bitte geben Sie Ihren Namen ein.';
}
if (values.comment.trim().length < 10) {
errors.comment = 'Der Kommentar muss mindestens 10 Zeichen lang sein.';
}
return errors;
}
function ReviewForm({ onSubmitReview }) {
const [values, setValues] = useState(ANFANGSWERTE);
const [errors, setErrors] = useState({});
function handleChange(event) {
const { name, value } = event.target;
setValues({ ...values, [name]: value });
}
function handleSubmit(event) {
event.preventDefault();
const validationErrors = validate(values);
setErrors(validationErrors);
if (Object.keys(validationErrors).length === 0) {
onSubmitReview(values);
setValues(ANFANGSWERTE);
}
}
return (
<form onSubmit={handleSubmit} className="review-form">
<div className="form-field">
<label htmlFor="name">Name</label>
<input
id="name"
name="name"
value={values.name}
onChange={handleChange}
/>
{errors.name && <p className="form-error">{errors.name}</p>}
</div>
<div className="form-field">
<label htmlFor="rating">Bewertung</label>
<select id="rating" name="rating" value={values.rating} onChange={handleChange}>
{[5, 4, 3, 2, 1].map((stern) => (
<option key={stern} value={stern}>{stern} {stern === 1 ? 'Stern' : 'Sterne'}</option>
))}
</select>
</div>
<div className="form-field">
<label htmlFor="comment">Kommentar</label>
<textarea
id="comment"
name="comment"
value={values.comment}
onChange={handleChange}
/>
{errors.comment && <p className="form-error">{errors.comment}</p>}
</div>
<button type="submit">Bewertung abschicken</button>
</form>
);
}
export default ReviewForm;Ein paar Details, die hier zusammenspielen: name="name"/name="rating" auf jedem Feld erlaubt EINEN gemeinsamen handleChange für alle Felder – event.target.name verrät, welches Feld sich geändert hat, und {{ ...values, [name]: value }} ("computed property name") aktualisiert gezielt nur dieses eine Feld im Objekt, ohne die anderen zu verlieren.
event.preventDefault() ist entscheidend: Ohne diese Zeile würde der Browser beim Absenden eines <form> standardmäßig die komplette Seite neu laden (der klassische, vor-JavaScript-Weg, Formulare zu verarbeiten) – das würde unseren gesamten React-State verlieren.
Zwei kleine, aber wichtige HTML-Unterschiede
| HTML | React |
|---|---|
<label for="name"> | <label htmlFor="name"> – for ist wie class ein reserviertes JS-Wort |
<option selected> | ausgewählte Option wird über value am <select> selbst gesteuert, nicht am <option> |
Tipp: Diese Datei ist noch nicht im Browser sichtbar – wir binden sie erst in Kapitel 16 (Real-World Forms) in unsere echte Seite ein, sobald wir mit Routing (Phase 4) auch eine eigene Produktdetail-Unterseite dafür haben.