10 — Formulaires

🔍 Composants contrôlés vs non-contrôlés

ContrôléNon-contrôlé
Valeur gérée parReact (state)Le DOM
Accès à la valeurVia le stateVia useRef
Recommandé ?✅ OuiCas spécifiques

En React, on préfère les composants contrôlés : la valeur de l'input est liée au state.

📐 Input contrôlé — le pattern de base

function MonInput() {
    const [valeur, setValeur] = useState('');

    return (
        <input 
            type="text"
            value={valeur}              // React contrôle la valeur
            onChange={(e) => setValeur(e.target.value)}  // Met à jour le state
        />
    );
}

Le cycle d'un input contrôlé :

  1. L'utilisateur tape une lettre.
  2. onChange se déclenche avec e.target.value.
  3. setValeur() met à jour le state.
  4. React re-rend avec la nouvelle valeur.
  5. L'input affiche la nouvelle valeur depuis le state.

📋 Différents types d'inputs

Input texte & nombre

const [nom, setNom] = useState('');
const [age, setAge] = useState(0);

<input type="text" value={nom} onChange={e => setNom(e.target.value)} />
<input type="number" value={age} onChange={e => setAge(Number(e.target.value))} />

Textarea

const [bio, setBio] = useState('');

// En React, textarea utilise value (pas de contenu entre les balises)
<textarea value={bio} onChange={e => setBio(e.target.value)} />

Select

const [pays, setPays] = useState('fr');

<select value={pays} onChange={e => setPays(e.target.value)}>
    <option value="fr">France</option>
    <option value="be">Belgique</option>
    <option value="ch">Suisse</option>
</select>

Checkbox

const [accepte, setAccepte] = useState(false);

<label>
    <input 
        type="checkbox"
        checked={accepte}           // checked, pas value
        onChange={e => setAccepte(e.target.checked)}  // checked, pas value
    />
    J'accepte les conditions
</label>

Radio

const [genre, setGenre] = useState('');

<label>
    <input type="radio" value="homme" checked={genre === 'homme'}
           onChange={e => setGenre(e.target.value)} /> Homme
</label>
<label>
    <input type="radio" value="femme" checked={genre === 'femme'}
           onChange={e => setGenre(e.target.value)} /> Femme
</label>

🧩 Formulaire multi-champs avec un seul state

Au lieu d'un useState par champ, on peut utiliser un objet :

function Inscription() {
    const [form, setForm] = useState({
        nom: '',
        email: '',
        password: '',
        pays: 'fr'
    });

    // Un seul handler pour tous les champs
    const handleChange = (e) => {
        const { name, value } = e.target;
        setForm(prev => ({ ...prev, [name]: value }));
        //                         ^^^^^^^^^
        //                 propriété calculée (computed property)
    };

    return (
        <form>
            <input name="nom" value={form.nom} onChange={handleChange} />
            <input name="email" value={form.email} onChange={handleChange} />
            <input name="password" type="password" value={form.password} onChange={handleChange} />
        </form>
    );
}
💡 Astuce : L'attribut name de l'input correspond à la clé dans l'objet state. [name]: value utilise une propriété calculée pour mettre à jour la bonne clé.

✅ Validation

function FormAvecValidation() {
    const [email, setEmail] = useState('');
    const [erreurs, setErreurs] = useState({});

    const valider = () => {
        const newErreurs = {};
        if (!email) newErreurs.email = 'Email requis';
        else if (!email.includes('@')) newErreurs.email = 'Email invalide';
        return newErreurs;
    };

    const handleSubmit = (e) => {
        e.preventDefault();
        const err = valider();
        setErreurs(err);
        if (Object.keys(err).length === 0) {
            alert('Formulaire valide !');
        }
    };
    // ...
}

🚀 Démo interactive

▼ Formulaire complet avec validation en temps réel

📝 Résumé

← 09 — useEffect 11 — Lifting State Up →