OUJOOD.COM
1. Composants Contrôlés (Controlled Components)
Un composant contrôlé est un composant où l'état de chaque champ de saisie est géré par React. Cela signifie que chaque modification dans un champ met à jour directement l'état du composant parent.
createRoot
par ReactDOM.render
.
import React, { useState } from 'react'; import { createRoot } from 'react-dom/client'; function Formulaire() { const [nom, setNom] = useState(''); const [email, setEmail] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); async function handleSubmit(event) { event.preventDefault(); // Empêche le rechargement de la page setIsSubmitting(true); try { // Simulation d'un appel API await new Promise(resolve => setTimeout(resolve, 1000)); alert(`Nom : ${nom}, Email : ${email}`); } catch (error) { console.error('Erreur lors de la soumission:', error); } finally { setIsSubmitting(false); } } return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="nom"> Nom : <input id="nom" type="text" value={nom} onChange={(e) => setNom(e.target.value)} disabled={isSubmitting} aria-required="true" /> </label> </div> <div> <label htmlFor="email"> Email : <input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} disabled={isSubmitting} aria-required="true" /> </label> </div> <button type="submit" disabled={isSubmitting}> {isSubmitting ? 'Envoi en cours...' : 'Envoyer'} </button> </form> ); } // React 18+ - Nouvelle API const container = document.getElementById('root'); const root = createRoot(container); root.render(<Formulaire />);
Explication détaillée :
-
import { createRoot } from 'react-dom/client';
:- Nous utilisons la nouvelle API de React 18 pour le rendu.
-
const [isSubmitting, setIsSubmitting] = useState(false);
:- Gestion de l'état de soumission pour améliorer l'UX.
-
htmlFor="nom"
etid="nom"
:- Association correcte entre labels et inputs pour l'accessibilité.
-
aria-required="true"
:- Indication pour les lecteurs d'écran que le champ est obligatoire.
-
disabled={isSubmitting}
:- Empêche les soumissions multiples accidentelles.
2. Composants Non Contrôlés avec useRef (Approche Moderne)
Contrairement aux composants contrôlés, les composants non contrôlés utilisent le DOM natif pour gérer les valeurs des champs de saisie. Avec les hooks, nous utilisons useRef
au lieu des références de classe.
import React, { useRef } from 'react'; import { createRoot } from 'react-dom/client'; function FormulaireNonControle() { const nomRef = useRef(null); const emailRef = useRef(null); const handleSubmit = (event) => { event.preventDefault(); // Empêche le rechargement de la page const nom = nomRef.current.value; // Récupère la valeur du champ "Nom" const email = emailRef.current.value; // Récupère la valeur du champ "Email" // Validation simple if (!nom.trim() || !email.trim()) { alert('Tous les champs sont obligatoires'); return; } alert(`Nom : ${nom}, Email : ${email}`); }; return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="nom-uncontrolled"> Nom : <input id="nom-uncontrolled" type="text" ref={nomRef} required aria-required="true" /> </label> </div> <div> <label htmlFor="email-uncontrolled"> Email : <input id="email-uncontrolled" type="email" ref={emailRef} required aria-required="true" /> </label> </div> <button type="submit">Envoyer</button> </form> ); } const container = document.getElementById('root'); const root = createRoot(container); root.render(<FormulaireNonControle />);
Explication détaillée :
-
import React, { useRef } from 'react';
:- Nous importons le hook
useRef
pour créer des références.
- Nous importons le hook
-
const nomRef = useRef(null);
:- Crée une référence moderne avec les hooks au lieu de
React.createRef()
.
- Crée une référence moderne avec les hooks au lieu de
-
required
:- Utilise la validation HTML5 native en complément de la validation JavaScript.
-
nomRef.current.value
:- Accède directement à la valeur du DOM, comme avec les classes mais de manière plus moderne.
3. Validation Avancée avec Hook Personnalisé
Pour des formulaires plus robustes, nous pouvons créer un hook personnalisé qui gère la validation, l'état et la soumission de manière réutilisable :
import React, { useState, useCallback } from 'react'; import { createRoot } from 'react-dom/client'; // Hook personnalisé pour la gestion des formulaires function useForm(initialValues, validationRules) { const [values, setValues] = useState(initialValues); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); // Fonction de validation const validateField = useCallback((name, value) => { const rules = validationRules[name]; if (!rules) return null; if (rules.required && !value.trim()) { return `Le champ ${name} est obligatoire`; } if (rules.minLength && value.length < rules.minLength) { return `${name} doit contenir au moins ${rules.minLength} caractères`; } if (rules.pattern && !rules.pattern.test(value)) { return rules.message || `Format de ${name} invalide`; } return null; }, [validationRules]); // Gestion des changements de valeur const handleChange = useCallback((name, value) => { setValues(prev => ({ ...prev, [name]: value })); // Validation en temps réel const error = validateField(name, value); setErrors(prev => ({ ...prev, [name]: error })); }, [validateField]); // Gestion de la soumission const handleSubmit = useCallback(async (onSubmit) => { setIsSubmitting(true); // Validation complète const newErrors = {}; Object.keys(values).forEach(key => { const error = validateField(key, values[key]); if (error) newErrors[key] = error; }); setErrors(newErrors); if (Object.keys(newErrors).length === 0) { try { await onSubmit(values); } catch (error) { console.error('Erreur de soumission:', error); } } setIsSubmitting(false); }, [values, validateField]); return { values, errors, isSubmitting, handleChange, handleSubmit }; } // Composant formulaire utilisant le hook function FormulaireAvecValidationAvancee() { const validationRules = { nom: { required: true, minLength: 2, pattern: /^[a-zA-ZÀ-ÿ\s]+$/, message: "Le nom ne doit contenir que des lettres" }, email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: "Format d'email invalide" } }; const { values, errors, isSubmitting, handleChange, handleSubmit } = useForm( { nom: '', email: '' }, validationRules ); const onSubmit = async (data) => { // Simulation d'appel API await new Promise(resolve => setTimeout(resolve, 1500)); alert(`Données validées et envoyées:\nNom: ${data.nom}\nEmail: ${data.email}`); }; return ( <form onSubmit={(e) => { e.preventDefault(); handleSubmit(onSubmit); }}> <div> <label htmlFor="nom-advanced"> Nom * : <input id="nom-advanced" type="text" value={values.nom} onChange={(e) => handleChange('nom', e.target.value)} aria-invalid={!!errors.nom} aria-describedby={errors.nom ? 'nom-error' : undefined} disabled={isSubmitting} /> </label> {errors.nom && ( <p id="nom-error" style={{ color: 'red', fontSize: '14px', margin: '5px 0' }} role="alert"> {errors.nom} </p> )} </div> <div> <label htmlFor="email-advanced"> Email * : <input id="email-advanced" type="email" value={values.email} onChange={(e) => handleChange('email', e.target.value)} aria-invalid={!!errors.email} aria-describedby={errors.email ? 'email-error' : undefined} disabled={isSubmitting} /> </label> {errors.email && ( <p id="email-error" style={{ color: 'red', fontSize: '14px', margin: '5px 0' }} role="alert"> {errors.email} </p> )} </div> <button type="submit" disabled={isSubmitting}> {isSubmitting ? 'Validation en cours...' : 'Envoyer'} </button> </form> ); } const container = document.getElementById('root'); const root = createRoot(container); root.render(<FormulaireAvecValidationAvancee />);
Explication détaillée :
-
useForm(initialValues, validationRules)
:- Hook personnalisé qui encapsule toute la logique de formulaire.
- Accepte des valeurs initiales et des règles de validation.
-
useCallback
:- Optimise les performances en mémorisant les fonctions.
-
aria-invalid
etaria-describedby
:- Améliorent l'accessibilité en indiquant les erreurs aux lecteurs d'écran.
-
role="alert"
:- Annonce automatiquement les messages d'erreur aux utilisateurs de lecteurs d'écran.
-
pattern: /^[a-zA-ZÀ-ÿ\s]+$/
:- Validation par expression régulière incluant les caractères accentués français.
4. Gestion de Différents Types de Champs
Les formulaires modernes incluent souvent différents types de champs. Voici comment les gérer efficacement :
import React, { useState } from 'react'; import { createRoot } from 'react-dom/client'; function FormulaireComplet() { const [formData, setFormData] = useState({ nom: '', email: '', age: '', pays: '', newsletter: false, genre: '', commentaires: '' }); const handleChange = (e) => { const { name, value, type, checked } = e.target; setFormData(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value })); }; const handleSubmit = (e) => { e.preventDefault(); console.log('Données du formulaire:', formData); alert('Formulaire soumis ! Voir la console pour les détails.'); }; return ( <form onSubmit={handleSubmit}> {/* Champ texte */} <div> <label htmlFor="nom-complet">Nom complet :</label> <input id="nom-complet" name="nom" type="text" value={formData.nom} onChange={handleChange} required /> </div> {/* Champ email */} <div> <label htmlFor="email-complet">Email :</label> <input id="email-complet" name="email" type="email" value={formData.email} onChange={handleChange} required /> </div> {/* Champ numérique */} <div> <label htmlFor="age">Âge :</label> <input id="age" name="age" type="number" min="13" max="120" value={formData.age} onChange={handleChange} /> </div> {/* Select */} <div> <label htmlFor="pays">Pays :</label> <select id="pays" name="pays" value={formData.pays} onChange={handleChange}> <option value="">Sélectionnez un pays</option> <option value="france">France</option> <option value="belgique">Belgique</option> <option value="suisse">Suisse</option> <option value="canada">Canada</option> </select> </div> {/* Checkbox */} <div> <label> <input name="newsletter" type="checkbox" checked={formData.newsletter} onChange={handleChange} /> S'abonner à la newsletter </label> </div> {/* Radio buttons */} <fieldset> <legend>Genre :</legend> <label> <input name="genre" type="radio" value="homme" checked={formData.genre === 'homme'} onChange={handleChange} /> Homme </label> <label> <input name="genre" type="radio" value="femme" checked={formData.genre === 'femme'} onChange={handleChange} /> Femme </label> <label> <input name="genre" type="radio" value="autre" checked={formData.genre === 'autre'} onChange={handleChange} /> Autre </label> </fieldset> {/* Textarea */} <div> <label htmlFor="commentaires">Commentaires :</label> <textarea id="commentaires" name="commentaires" value={formData.commentaires} onChange={handleChange} rows="4" cols="50" placeholder="Vos commentaires ici..." ></textarea> </div> <button type="submit">Envoyer le formulaire</button> </form> ); } const container = document.getElementById('root'); const root = createRoot(container); root.render(<FormulaireComplet />);
5. Meilleures Pratiques pour les Formulaires React
Pour créer des formulaires React efficaces et maintenables, voici quelques conseils modernisés :
- Privilégiez les composants contrôlés : Ils offrent une meilleure intégration avec React et facilitent la validation en temps réel.
- Utilisez toujours
event.preventDefault()
: Cela évite le rechargement de la page lors de la soumission du formulaire. - Implémentez une validation côté client robuste : Utilisez des expressions régulières et des règles métier appropriées.
- Gérez l'état de soumission : Désactivez les champs et le bouton pendant le traitement pour éviter les doublons.
- Pensez à l'accessibilité : Utilisez les attributs ARIA, associez correctement les labels, et gérez le focus.
- Optimisez les performances : Utilisez
useCallback
etuseMemo
pour éviter les re-rendus inutiles. - Créez des hooks personnalisés : Réutilisez la logique de formulaire dans toute votre application.
6. Bibliothèques Recommandées
Pour des formulaires complexes, considérez ces bibliothèques populaires :
- React Hook Form : Performante, avec validation intégrée et faible boilerplate.
- Formik : Solution complète avec validation Yup intégrée.
- React Final Form : Basée sur une architecture subscription pour les performances.
Conclusion
Vous avez maintenant appris à gérer des formulaires modernes dans React en utilisant les hooks, la validation avancée, et les meilleures pratiques d'accessibilité. Les techniques présentées vous permettront de créer des formulaires robustes et réutilisables.
Prochain chapitre : React Router