SyntaxStudy
Sign Up
React Form Submission and Async Validation
React Beginner 1 min read

Form Submission and Async Validation

Handling form submission involves preventing the default browser behaviour, running synchronous or asynchronous validation, and then sending the data to an API. Tracking submission state — idle, submitting, error, success — with a state variable lets you disable the submit button during the request and display appropriate feedback. Async validation such as checking whether a username is already taken requires debouncing: waiting until the user pauses typing before firing the request. Without debouncing every keypress triggers a network request, which floods the server and produces a poor user experience. You can implement basic debouncing with a useEffect that sets a timeout and clears it on every keystroke. After a successful submission you typically reset the form, navigate to a new page, or show a success banner. Resetting the state to initial values is as simple as calling setFields with the initial object. If the submission fails, display the error message near the relevant field or in a global alert area.
Example
import { useState, useEffect } from 'react';

function UniqueUsernameForm() {
    const [username, setUsername] = useState('');
    const [usernameError, setUsernameError] = useState('');
    const [status, setStatus] = useState('idle'); // idle | checking | available | taken

    // Debounced availability check
    useEffect(() => {
        if (username.length < 3) { setStatus('idle'); return; }
        setStatus('checking');
        const timer = setTimeout(async () => {
            const res  = await fetch(`/api/check-username?q=${username}`);
            const data = await res.json();
            setStatus(data.available ? 'available' : 'taken');
            setUsernameError(data.available ? '' : 'Username is taken');
        }, 400);
        return () => clearTimeout(timer);
    }, [username]);

    const [submitStatus, setSubmitStatus] = useState('idle');

    async function handleSubmit(e) {
        e.preventDefault();
        if (status !== 'available') return;
        setSubmitStatus('loading');
        try {
            await fetch('/api/register', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username }),
            });
            setSubmitStatus('success');
        } catch {
            setSubmitStatus('error');
        }
    }

    return (
        <form onSubmit={handleSubmit}>
            <input
                value={username}
                onChange={e => setUsername(e.target.value)}
                placeholder="Choose a username"
            />
            {status === 'checking'   && <span>Checking…</span>}
            {status === 'available'  && <span style={{ color: 'green' }}>Available!</span>}
            {usernameError           && <span style={{ color: 'red' }}>{usernameError}</span>}
            <button type="submit" disabled={status !== 'available' || submitStatus === 'loading'}>
                {submitStatus === 'loading' ? 'Registering…' : 'Register'}
            </button>
            {submitStatus === 'success' && <p>Done!</p>}
        </form>
    );
}