Gestion simplifiée de formulaires avec FORMIK
Concept de la gestion d'un formulaire
Tous les tutos présents dans la catégorie "BONUS" ne sont pas obligatoires. Ils visent simplement à vous montrer quelques outils permettant de simplifier la création de code REACT. Tout ce qui est ici ne sera pas au devoir mais pourrait vous aider à faire votre porjet.
Formik est une bibliothèque pour React qui facilite la création et la gestion de formulaires complexes. Elle simplifie des tâches courantes comme la gestion des champs de formulaire, des erreurs de validation et des événements. Son principal objectif est de réduire la complexité et le code associé à la gestion des formulaires dans React.
Nous verrons dans ce tutoriel que Formik permet de :
- Gérer l'état des champs (useState ou autre gestion d'état).
- Valider les données saisies par l'utilisateur.
- Gérer les erreurs de validation.
- Traiter les soumissions du formulaire.
L'exemple ci-dessous détaille le code d'un composant contenant uniqument
import React from "react";
import { useFormik } from "formik";
type FormValues = {
email: string;
}
const EmailForm: React.FC = () => {
//hook fournit par Formik
//Il crée le formulaire à partir du type créé (ici FormValues)
const formik = useFormik<FormValues>({
//initialValues est un objet qui définit les valeurs initiales du formulaire.
initialValues: {
//Ici on définit une valeur par défaut
email: "",
},
//fonction appelée au moment de la soumission du formulaire
validate: (values: FormValues) => {
//Partial<FormValues> est utilisé pour indiquer que toutes les propriétés de l'objet sont optionnelles.
//Cela permet de ne pas valider tous les champs à la fois.
//Cette fonction est appelée à chaque changement (onChange)
const errors: Partial<FormValues> = {};
//Vous codez ici la logique d'erreurs que vous désirez
// ici j'ai géré le cas ou l'email n'est pas saisi
//puis s'il a été saisi je vérifie si le mail est correct
if (!values.email) {
errors.email = "L'email est requis";
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = "L'email est invalide";
}
return errors;
},
//CallBack suite au submit du formulaire
onSubmit: (values: FormValues) => {
console.log("Formulaire soumis avec :", values);
},
});
return (
{/*formik.handleSubmit permet d'empecher le rechargement de la page,
appelle validate, et appelle onSubmit*/}
<form onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="email">Email :</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange} // Formik met à jour la valeur de `email` dans `values` à chaque fois que vous appuyez sur voter clavier
onBlur={formik.handleBlur} // Formik marque `email` comme "touché" dans `touched` quand on quitte le champ (tabulation ou clique)
value={formik.values.email} //cette variable est mise à jour automatiquement (pas besoin de faire de useState)
/>
{/*formik.touched vaut vrai si on a quitté le champ*/}
{/*si on clique sur le champ mail et qu'on le quitte sans l'avoir correctement rempli, une erreur s'affiche*/}
{formik.touched.email && formik.errors.email ? (
<div style={{ color: "red" }}>{formik.errors.email}</div>
) : null}
</div>
<button type="submit">Envoyer</button>
</form>
);
};
export default EmailForm;
Nous avons écrit ce code.
onChange={formik.handleChange} // Formik met à jour la valeur de `email` dans `values`
onBlur={formik.handleBlur} // Formik marque `email` comme "touché" dans `touched`
Ca peut être déroutant car nous n'avons pas codé de Callback pour handleChange
et handleBlur
.
L'avantage majeur de Formik
est qu'il gère la logique de validation et de mise à jour de l'état pour vous, sans que vous ayez besoin de créer une fonction manuellement pour chaque champ.
Vous n'avez donc pas besoin de gérer vous même les CallBack.
Exemple approfondi en couplant Formik et Yup
Présentation de Yup
Yup est une bibliothèque de validation de schémas très puissante et facile à utiliser. Son principal objectif est de fournir une manière déclarative de définir les règles de validation pour des objets, comme les données d’un formulaire. Voici ses avantages :
Validation structurée et lisible :
- Les schémas définis avec
Yup
sont clairs et faciles à lire, ce qui rend les règles de validation simples à maintenir. Par exemple, au lieu de définir manuellement plusieurs conditions dans une fonction, vous utilisez des méthodes intuitives commestring()
,required()
,max()
.
- Les schémas définis avec
Messages d'erreur personnalisés :
- Chaque règle de validation peut inclure des messages d’erreur adaptés, ce qui améliore l’expérience utilisateur.
Intégration facile avec Formik :
- Yup est souvent utilisé avec Formik pour simplifier la validation des formulaires. Formik accepte directement un schéma Yup, éliminant le besoin de réinventer la roue.
Validation synchrone et asynchrone :
- Yup prend en charge les validations asynchrones, ce qui est utile pour des vérifications plus complexes comme vérifier qu'un nom d'utilisateur est déjà pris via une API.
Exemple avec Formik
Avec Yup
, au lieu d'écrire manuellement une fonction validate
, vous définissez simplement un schéma, ce qui réduit la complexité de votre code.
const validationSchema = Yup.object({
email: Yup.string().email('Email invalide').required('Email obligatoire'),
password: Yup.string().min(8, 'Au moins 8 caractères').required('Mot de passe requis'),
});
Exemple complet
L'exemple ci-dessous permet de créer un formulaire d'inscription. Il contient 4 champs avec différents niveau de contrôles sur les contenus.
Si le code marche bien, vous devriez voir un résultat proche de celui-ci.
import React from "react";
import { useFormik } from "formik";
import * as Yup from "yup";
import "./SignupForm.css"; // Importation du fichier CSS
type FormValues = {
firstName: string;
lastName: string;
email: string;
avatar: string; // qui n'est pas requis
}
const avatars = ["😀", "😎", "😂", "😍", "🤔", "😇", "🥳", "😭", "🤩", "😡"];
const SignupForm: React.FC = () => {
//initialisation
const formik = useFormik<FormValues>({
initialValues: {
firstName: "",
lastName: "",
email: "",
avatar: "",
},
//définition du schéma de validation. seul l'avatar n'est pas obligatoire
validationSchema: Yup.object({
//nombre de caractères entre 2 et 15
firstName: Yup.string()
.min(2, "Must be at least 2 characters")
.max(15, "Must be 15 characters or less")
.required("Required"),
//nombre de caractères entre 1 et 20
lastName: Yup.string()
.max(20, "Must be 20 characters or less")
.required("Required"),
//l'email doit pêtre valide et est obligatoire
email: Yup.string().email("Invalid email address").required("Required"),
//pas de contraintes sur l'avatar
// avatar: Yup.string().required("Avatar selection is required"),
}),
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
//il faut définir chaque input et comment il doit se mettre à jour (c'est presque toujours la même chose)
<form className="form-container" onSubmit={formik.handleSubmit}>
<div className="form-group">
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
className={formik.touched.firstName && formik.errors.firstName ? "input-error" : ""}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div className="error-message">{formik.errors.firstName}</div>
) : null}
</div>
<div className="form-group">
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
className={formik.touched.lastName && formik.errors.lastName ? "input-error" : ""}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div className="error-message">{formik.errors.lastName}</div>
) : null}
</div>
<div className="form-group">
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
className={formik.touched.email && formik.errors.email ? "input-error" : ""}
/>
{formik.touched.email && formik.errors.email ? (
<div className="error-message">{formik.errors.email}</div>
) : null}
</div>
<div className="form-group">
<label htmlFor="avatar">Choose an Avatar</label>
<select
id="avatar"
name="avatar"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.avatar}
className={
formik.touched.avatar && formik.errors.avatar ? "input-error" : ""
}
>
{/*petite spécificité ici. avec map, on génère toutes les options*/}
<option value="">Select an avatar</option>
{avatars.map((emoji, index) => (
<option key={index} value={emoji}>
{emoji}
</option>
))}
</select>
{/*Si jamais vous ajoutez une contrainte sur l'avatar par exemple
avatar: Yup.string().required("Avatar selection is required"),
il faudrait alors ajouter le code ci-dessous*/}
{/* {formik.touched.avatar && formik.errors.avatar ? (
<div className="error-message">{formik.errors.avatar}</div>
) : null} */}
</div>
<button type="submit">Envoyer</button>
</form>
);
};
export default SignupForm;