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.

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.

TypeScript useForm.js
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.

TypeScript Validierungsfunktion
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.

TypeScript FormComponent.jsx
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.

React Custom Hook - Beispiel Verwendung mit Formular

/ Weiter

Zurück zu Custom Hooks

Zur Übersicht