Contrôler les champs du formulaire
Objectif : Synchroniser les champs montant, catégorie et date avec l’état React.
Un formulaire de dépenses doit conserver ce que vous saisissez pendant que vous changez de champ. Si React ne connaît pas ces valeurs, il devient difficile de les valider ou de les transmettre ensuite de manière fiable. Vous allez donc contrôler trois champs — montant, catégorie et date — en faisant de l’état React leur source d’affichage.
Vous avez déjà vu `useState` dans les leçons précédentes : ici, chaque valeur de champ est stockée comme une chaîne. C’est particulièrement important pour le montant, car un champ HTML de type `number` peut être vide pendant la saisie ; garder sa valeur sous forme de chaîne évite de confondre ce cas avec le nombre zéro. La conversion en nombre sera effectuée au moment où vous traiterez la soumission, dans une prochaine leçon.
Relier chaque champ à l’état React
Un champ contrôlé reçoit sa valeur depuis l’état avec la propriété `value`. Son événement `onChange` met ensuite cet état à jour à partir de la valeur saisie. Le cycle est direct : le navigateur signale une modification, le gestionnaire met à jour l’état, puis React réaffiche le champ avec la nouvelle valeur.
Le composant ci-dessous contrôle les trois champs demandés. Il reçoit les catégories par propriétés afin de proposer les mêmes valeurs que le reste de l’application ; les catégories elles-mêmes sont définies par le type déjà établi dans « Définir le modèle d’une dépense ». Le champ libellé reste présent pour respecter le formulaire du projet, mais son contrôle n’est pas l’objectif de cette leçon.
import { useState, type ChangeEvent } from "react";
import type { CategorieDepense } from "../types";
type FormulaireDepenseProps = {
categories: CategorieDepense[];
};
export default function FormulaireDepense({
categories,
}: FormulaireDepenseProps) {
const [montant, setMontant] = useState("");
const [categorie, setCategorie] = useState<CategorieDepense | "">("");
const [date, setDate] = useState("");
function modifierMontant(event: ChangeEvent<HTMLInputElement>) {
setMontant(event.currentTarget.value);
}
function modifierCategorie(event: ChangeEvent<HTMLSelectElement>) {
const valeur = event.currentTarget.value;
if (categories.includes(valeur as CategorieDepense)) {
setCategorie(valeur as CategorieDepense);
} else {
setCategorie("");
}
}
function modifierDate(event: ChangeEvent<HTMLInputElement>) {
setDate(event.currentTarget.value);
}
return (
<section className="depenses__panneau" aria-labelledby="titre-saisie">
<h2 className="depenses__titre" id="titre-saisie">
Ajouter une dépense
</h2>
<form className="depenses__formulaire">
<label className="depenses__champ" htmlFor="libelle">Libellé</label>
<input id="libelle" name="libelle" type="text" required />
<label className="depenses__champ" htmlFor="montant">Montant</label>
<input
className="depenses__champ"
id="montant"
name="montant"
type="number"
min="0.01"
step="0.01"
required
value={montant}
onChange={modifierMontant}
/>
<label className="depenses__champ" htmlFor="categorie">Catégorie</label>
<select
className="depenses__champ"
id="categorie"
name="categorie"
required
value={categorie}
onChange={modifierCategorie}
>
<option value="">Choisir une catégorie</option>
{categories.map((element) => (
<option key={element} value={element}>{element}</option>
))}
</select>
<label className="depenses__champ" htmlFor="date">Date</label>
<input
className="depenses__champ"
id="date"
name="date"
type="date"
required
value={date}
onChange={modifierDate}
/>
<p className="depenses__erreur" role="alert"></p>
<button type="button">Ajouter</button>
</form>
</section>
);
}Comprendre les types des événements
En TypeScript, `ChangeEvent` précise que l’événement vient d’un champ de saisie, alors que `ChangeEvent` indique qu’il vient d’un menu déroulant. Cette précision donne accès à `currentTarget.value` avec le bon type et permet à l’éditeur de signaler une mauvaise utilisation. Importez `ChangeEvent` comme type, car il ne sert qu’à la vérification TypeScript et n’est pas nécessaire à l’exécution dans le navigateur.
Dans le JSX, `value={montant}`, `value={categorie}` et `value={date}` relient chaque champ à son état. Quand vous saisissez un montant, sélectionnez une catégorie ou choisissez une date, le gestionnaire correspondant met à jour cette valeur ; le champ affiche alors l’état courant. Les trois valeurs restent ainsi disponibles dans le composant sans être recopiées dans une autre variable d’état.
Vérifier le comportement dans la page
Remplacez le contenu du fichier `src/composants/FormulaireDepense.tsx` par le composant complet ci-dessus. Dans `App.tsx`, conservez le chargement, la liste et les autres sections déjà présents ; importez le formulaire et transmettez-lui la collection `categories` que votre projet utilise. Placez-le à l’emplacement de la section de saisie existante, sans créer un second formulaire ni déplacer les dépenses hors de leur état actuel.
Après le rendu, testez le champ montant en entrant une valeur puis en l’effaçant : il doit pouvoir afficher aussi une chaîne vide. Choisissez ensuite une catégorie et une date ; chacune reste sélectionnée tant que vous ne la modifiez pas. Aucun élément de liste ni récapitulatif ne change encore, puisque cette étape contrôle la saisie uniquement et ne crée pas de dépense.
À retenir
- Un champ contrôlé reçoit sa valeur depuis l’état avec `value`.
- `onChange` transmet la saisie à un gestionnaire qui met à jour cet état.
- Conservez le montant sous forme de chaîne pendant la saisie, notamment pour représenter le champ vide.
- Typez les événements selon l’élément HTML concerné et vérifiez les valeurs de catégorie avant de les traiter comme une catégorie valide.
Dans la prochaine leçon, vous empêcherez les saisies invalides d’être traitées et associerez des messages d’erreur aux champs.