navigation Navigation


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.

    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.

    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.

    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