Beispiel mit Formular
Die Formularverarbeitung in React-Anwendungen kann schnell komplex und repetitiv werden. Custom Hooks bieten einen eleganten Ansatz, um Formularlogik zu kapseln und wiederzuverwenden. Diese Seite demonstriert anhand praktischer Beispiele, wie maßgeschneiderte Hooks die Handhabung von Formularen vereinfachen, den Code strukturieren und die Wartbarkeit verbessern. Von einfachen Eingabefeldern bis zu komplexen Validierungsszenarien zeigt dieser Leitfaden effektive Implementierungsmuster für die moderne React-Entwicklung auf.
Inhaltsverzeichnis
Formulare sind ein häufiger Anwendungsfall in React-Anwendungen. Anstatt in jeder Komponente die gleiche Logik für Formular-Handling zu schreiben, erstellen wir einen wiederverwendbaren Hook.
In diesem Beispiel wird ein React-Component RegistrationForm
gezeigt, das ein Registrierungsformular darstellt. Dabei verwenden wir ein Custom Hook useForm
, der den gesamten Zustand und die Logik zur Formularverwaltung kapselt.
Wir gehen in diesem Beispiel Schritt für Schritt und aufbauend vor.
Custom Hook
Zuerst definieren wir unseren Custom Hook mit allen Zustandswerten und Handler-Funktionen.
function useForm(initialValues, validateFunction) {
// State für Formularwerte
const [fieldValues, setFieldValues] = useState(initialValues);
// State für Validierungsfehler - Ein Objekt
const [fieldErrors, setFieldErrors] = useState({});
// State für 'touched' Felder - Ein Objekt
const [fieldsTouched, setFieldsTouched] = useState({});
// Handler für Änderungen in Eingabefeldern
handleFieldChange = (event) => {
const {
name,
value,
type,
checked
} = event.target;
// Bei Checkboxen 'checked', sonst 'value'
const newValue = type === 'checkbox' ? checked : value;
setFieldValues(prevValues => {
...prevValues,
[name]: newValue
});
// Validierung durchführen
if (validateFunction) {
const validationErrors = validateFunction({
...fieldValues,
[name]: newValue
});
setFieldErrors(validationErrors);
}
};
// Handler für Blur-Events (Feld verlassen)
const handleFieldBlur = (event) => {
const { name } = event.target;
setFieldsTouched(prevFieldsTouched => ({
...prevFieldsTouched,
[name]: true
}));
};
// Handler für Reset des Formulars
const handleFormReset = () => {
setFieldValues(initialValues);
setFieldErrors({});
setFieldsTouched({});
};
// Handler (Funktion) zum programmatischen Setzen
const setFieldValue = (name, value) => {
setFieldValues(prevFieldValues => ({
...prevFieldValues,
[name]: value
}));
};
return {
fieldValues,
fieldErrors,
fieldsTouched,
handleFieldChange,
handleFieldBlur,
handleFormReset,
setFieldValue
};
}
export default useForm;
Was haben wir hier? Betrachten wir zuerst die Parameter, welche von useForm
angenommen werden.
initialValues
: Objekt, das Startwerte für alle Formularfelder enthält.validateFunction
: Eine Funktion, die die aktuellen Werte annimmt und Felder zurückgibt.
Zustandswerte
fieldValues
: Aktuelle Werte der Formularfelder als Objekt.fieldErrors
: Validierungsfehler als Objekt.fieldsTouched
: Welcher Felder vom Benutzer schon berührt (fokussiert und wieder verlassen) wurden als Objekt
Handler-Funktionen
Wir brauchen ebenfalls ein paar Funktionen später, die bestimmte Sachen mit den Formular-Feldern tun.
handleFieldChange(event)
: Wird aufgerufen, wenn ein Input-Feld geändert wird. Hier versuchen wir unterschiedliche Typen von Feldern zu berücksichtigen.handleFieldBlur(event)
: Wird aufgerufen, wenn ein Feld den Fokus verliert. Dabei wird das Feld als ‘touched’ markiert, damit potenzielle Fehler erst nach Verlassen des Feldes angezeigt werden.handleFormReset()
: Setzt alle States wieder auf Initialwerte zurück.setFieldValue(name, value)
: Ermöglicht, von außen programmatisch Werte zu setzen
Validierungsfunktion
Die Validierungsfunktion soll natürlich für das jeweilige Formular gesondert geschrieben und angepasst werden. In diesem Beispiel behandelt unsere Validierungsfunktion nur die Felder, welche im Formular vorkommen werden.
Wo wir die Funktion definieren, ist generell (oder technisch gesehen) fast egal. Sinn macht es allerdings, sie in der Nähe des Formulars zu behalten, zu dem sie ein Bezug hat bzw. für welches sie gedacht ist.
function validateRegistrationForm(fieldValues) {
// Fehler-Objekt (initial leer)
const errors = {};
// Benutzername (fieldUsername)
if (!fieldValues.fieldUsername) {
errors.fieldUsername = 'Benutzername ist erforderlich';
} else if (fieldValues.fieldUsername.length < 3) {
errors.fieldUsername = 'Benutzername muss mindestens 3 Zeichen lang sein';
}
// E-Mail (fieldEmail)
if (!fieldValues.fieldEmail) {
errors.fieldEmail = 'E-Mail ist erforderlich';
} else if (!/\S+@\S+\.\S+/.test(fieldValues.fieldEmail)) {
errors.fieldEmail = 'E-Mail Adresse ist ungültig';
}
// Passwort (fieldPassword)
if (!fieldValues.fieldPassword) {
errors.fieldPassword = 'Passwort ist erforderlich';
} else if (fieldValues.fieldPassword.length < 6) {
errors.fieldPassword = 'Passwort muss mindestens 6 Zeichen lang sein';
}
// AGB (fieldTerms)
if (!fieldValues.fieldTerms) {
errors.fieldTerms = 'Sie müssen AGB akzeptieren';
}
return errors;
}
Formular Component
Nun bringen wir alles zusammen und werden unseren Custom Hook useForm
verwenden.
Wir fügen außerdem in diesem Component ein paar Stile hinzu. Diese können haben natürlich keine große Bedeutung für das Verständnis von useForm()
Custom Hook.
import useForm from './useForm';
function validateRegistrationForm(fieldValues) {
// Fehler-Objekt (initial leer)
const errors = {};
// Benutzername (fieldUsername)
if (!fieldValues.fieldUsername) {
errors.fieldUsername = 'Benutzername ist erforderlich';
} else if (fieldValues.fieldUsername.length < 3) {
errors.fieldUsername = 'Benutzername muss mindestens 3 Zeichen lang sein';
}
// E-Mail (fieldEmail)
if (!fieldValues.fieldEmail) {
errors.fieldEmail = 'E-Mail ist erforderlich';
} else if (!/\S+@\S+\.\S+/.test(fieldValues.fieldEmail)) {
errors.fieldEmail = 'E-Mail Adresse ist ungültig';
}
// Passwort (fieldPassword)
if (!fieldValues.fieldPassword) {
errors.fieldPassword = 'Passwort ist erforderlich';
} else if (fieldValues.fieldPassword.length < 6) {
errors.fieldPassword = 'Passwort muss mindestens 6 Zeichen lang sein';
}
// AGB (fieldTerms)
if (!fieldValues.fieldTerms) {
errors.fieldTerms = 'Sie müssen AGB akzeptieren';
}
return errors;
}
function FormComponent() {
// Initiale Werte für das Formular
const initialValues = {
fieldUsername: '',
fieldEmail: '',
fieldPassword: '',
fieldTerms: false
};
// Custom Hook
const {
fieldValues,
fieldErrors,
fieldsTouched,
handleFieldChange,
handleFieldBlur,
handleFormReset
} = useForm(initialValues, validateRegistrationForm);
// Handler für Formular-Submit
const handleFormSubmit = () => {
const allTouched = {};
Object.key(fieldValues).forEach(fieldKey => {
allTouched[fieldKey] = true;
});
// Simulation für Blur von Feldern
Object.keys(fieldValues).forEach(fieldKey => {
handleFieldBlur({ target: { name: fieldKey } });
});
// Prüfen, ob es Fehler gibt
const validationErrors = validateRegistrationForm(fieldValues);
if (Object.keys(validationErrors).length === 0) {
console.log('Formular abgesendet!');
console.log(JSON.stringify(fieldValues));
handleFormReset();
}
};
// Styles
const containerStyle = {
margin: '20px auto',
maxWidth: '500px',
padding: 20,
borderRadius: 8,
boxSizing: 'border-box'
};
const fieldStyle = {
marginBottom: 15
};
const labelStyle = {
display: 'block',
marginBottom: 4,
fontWeight: 'bold'
}
const inputStyle = {
width: '100%',
padding: 8,
border: '2px solid #cccccc',
borderRadius: 4,
fontSize: 14,
boxSizing: 'border-box'
};
const errorStyle = {
color: '#dc3545',
fontSize: 12,
marginTop: 5
};
const buttonStyle = {
backgroundColor: '#28a745',
color: '#ffffff',
padding: '10px 20px',
border: 'none',
borderRadius: 4,
fontSize: 16,
cursor: 'pointer',
marginRight: 10
};
const infoBoxStyle = {
backgroundColor: '#e7f3ff',
border: '2px solid #b3d9ff',
borderRadius: 4,
padding: 10,
marginBottom: 20,
fontSize: 14
};
return (
<div style={containerStyle}>
<h3>Registrierung mit useForm Custom Hook</h3>
<div style={infoBoxStyle}>
<strong>Hinweis:</strong> Dieser Custom Hook verwaltet Formularwerte, Validierung und Touch-Zustand.
</div>
<div style={fieldStyle}>
<label style={labelStyle}>Benutzername:</label>
<input
type="text"
name="fieldUsername"
value={fieldValues.fieldUsername}
onChange={handleFieldChange}
onBlur={handleFieldBlur}
placeholder="Mind. 3 Zeichen"
style={{
...inputStyle,
borderColor: fieldsTouched.fieldUsername && fieldErrors.fieldUsername ? '#dc3545' : '#ccc'
}}
/>
{fieldsTouched.fieldUsername && fieldErrors.fieldUsername && (
<div style={errorStyle}>{fieldErrors.fieldUsername}</div>
)}
</div>
<div style={fieldStyle}>
<label style={labelStyle}>E-Mail:</label>
<input
type="email"
name="fieldEmail"
value={fieldValues.fieldEmail}
onChange={handleFieldChange}
onBlur={handleFieldBlur}
placeholder="example@mail.com"
style={{
...inputStyle,
borderColor: fieldsTouched.fieldEmail && fieldErrors.fieldEmail ? '#dc3545' : '#ccc'
}}
/>
{fieldsTouched.fieldEmail && fieldErrors.fieldEmail && (
<div style={errorStyle}>{fieldErrors.fieldEmail}</div>
)}
</div>
<div style={fieldStyle}>
<label style={labelStyle}>Passwort:</label>
<input
type="password"
name="fieldPassword"
value={fieldValues.fieldPassword}
onChange={handleFieldChange}
onBlur={handleFieldBlur}
placeholder="Min. 6 Zeichen"
style={{
...inputStyle,
borderColor: fieldsTouched.fieldPassword && fieldErrors.fieldPassword ? '#dc3545' : '#ccc'
}}
/>
{fieldsTouched.fieldPassword && fieldErrors.fieldPassword && (
<div style={errorStyle}>{fieldErrors.fieldPassword}</div>
)}
</div>
<div style={fieldStyle}>
<label style={{
display: 'flex',
alignItems: 'center',
cursor: 'pointer'
}}>
<input
type="checkbox"
name="fieldTerms"
checked={fieldValues.fieldTerms}
onChange={handleFieldChange}
onBlur={handleFieldBlur}
style={{ marginRight: 8, cursor: 'pointer' }}
/>
Ich akzeptiere die AGB
</label>
{fieldsTouched.fieldTerms && fieldErrors.fieldTerms && (
<div style={errorStyle}>{fieldErrors.fieldTerms}</div>
)}
</div>
<div style={{
display: 'flex',
gap: 10,
alignItems: 'center'
}}>
<button
style={buttonStyle}
onClick={handleFormSubmit}
>
Registrieren
</button>
<button
style={{ ...buttonStyle, backgroundColor: '#6c757d' }}
onClick={handleFormReset}
>
Zurücksetzen
</button>
</div>
</div>
);
}
export default FormComponent;
Wenn wir nun unsere Applikation laufen lassen, werden wir ein Formular mit den angegebenen Feldern erhalten. Wenn wir versuchen ein Feld auszufüllen und wieder alles löschen, erhalten wir den Fehler eingeblendet.
Wenn wir alles korrekt ausfüllen und absenden, sehen wir in der Konsole die Formularwerte und die entsprechende Meldung.