Module 2Construire une application React
Chapitre 9 sur 18
Créer des formulaires
Les composants contrôlés, textarea, select, boutons radio et cases à cocher, et les TP de connexion et d'inscription.
- ≈ 190 min
- 8 leçons
- 1 vidéo
- 4 exercices
Module 2Construire une application React
Chapitre 9 sur 18
Les composants contrôlés, textarea, select, boutons radio et cases à cocher, et les TP de connexion et d'inscription.
Objectifs
Avec ce chapitre, tu sauras…
event.target.value (ou event.target.checked) ;Leçon 1
Les formulaires HTML sont différents des autres éléments : ils ont naturellement un état interne (ce qui est tapé dans le champ). Ce formulaire HTML accepte un nom :
<form>
<label>
Nom :
<input type="text" name="name" />
</label>
<input type="submit" value="Envoyer" />
</form>À l'envoi, il redirige vers une nouvelle page. Si c'est ce que tu veux, rien à faire en React. Mais en général, on veut gérer l'envoi avec une fonction JavaScript qui accède aux données saisies. La façon classique est d'utiliser des composants contrôlés.
Leçon 2
import { useState } from "react";
const NameForm = () => {
const [nom, setNom] = useState('');
const handleChange =(event)=>{
setNom(event.target.value);
}
const handleSubmit =(event)=>{
alert('Le nom a été soumis : ' + nom);
event.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<label>
Nom :
<input type="text" name="nom" value={nom} onChange={handleChange} />
</label>
<input type="submit" value="Envoyer" />
</form>
);
};
export default NameForm;import React from 'react';
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {nom: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({nom: event.target.value});
}
handleSubmit(event) {
alert('Le nom a été soumis : ' + this.state.nom);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Nom :
<input type="text" name="nom" value={this.state.nom} onChange={this.handleChange} />
</label>
<input type="submit" value="Envoyer" />
</form>
);
}
}
export default NameForm;Tape quelques caractères puis clique sur Envoyer : une boîte affiche « Le nom a été soumis : … ».
Vidéo · À regarder
FRApprendre React en 1 heure - Cours complet débutant
Comment Coder · YouTube
Leçon 3
Leçon 4
Leçon 5
Leçon 6
| Champ | Attribut contrôlé | Ce qu'on lit dans onChange |
|---|---|---|
<input type="text">, <textarea>, <select> | value | event.target.value |
<input type="checkbox"> | checked | event.target.checked |
<input type="radio"> | checked={choix === valeur} | event.target.value |
Leçon 7
Leçon 8
1Un composant contrôlé a…
2La « source unique de vérité » d'un champ contrôlé est…
3Pour une case à cocher, on lit…
4Dans une classe, this.handleChange.bind(this) sert à…
5En React, l'option sélectionnée d'un <select> se règle avec…
6Des boutons radio du même groupe ont…
7event.preventDefault() dans onSubmit…
L'essentiel du chapitre
value={x}), chaque saisie le met à jour (onChange) ; l'état est la source unique de vérité. value sans onChange = champ bloqué.<form onSubmit={handleSubmit}> + event.preventDefault().this.state, this.setState, .bind(this) dans le constructeur (ou fonctions fléchées) ; fonction : useState, pas de this.<textarea value={…}> ; <select value={…}> ou defaultValue ; radios : même name, checked ; cases : event.target.checked.errorMessages + {condition && <div className="error">…</div>} ; afficher la réussite : un état isSubmitted + ternaire.Ce que tu vas voir (7 min) : le chapitre « Les formulaires » : un formulaire dont les champs sont reliés à l'état (value + onChange), et l'envoi géré avec onSubmit et preventDefault. C'est le composant contrôlé de la section 2.
On crée un composant <TextArea> qui « améliore » le <textarea> classique. Il s'utilise ainsi :
<TextArea cols={40} rows={10} value="Tapez votre texte ici" />Comme pour un champ texte, il faut onChange pour que la saisie soit prise en compte, et l'état doit contenir la valeur. La prop value sert à initialiser l'état. Amélioration : au focus (clic dans le champ), le texte d'aide est effacé.
import { useState } from "react";
const FTextArea = (props) => {
const [message, setMessage] = useState(props.value);
const handlerChange=(event)=>{
setMessage(event.target.value);
}
const handlerFocus=()=>{
setMessage("");
}
return (
<textarea cols={props.cols}
rows={props.rows}
value={message}
onFocus={handlerFocus.bind(this)}
onChange={handlerChange.bind(this)} />
);
};
export default FTextArea;import React from 'react';
class TextArea extends React.Component {
constructor(props) {
super(props);
this.state = { message : props.value };
}
handlerChange(event) {
this.setState({message : event.target.value});
}
handlerFocus(event) {
this.setState({message : ""});
}
render() {
return (
<textarea cols={this.props.cols}
rows={this.props.rows}
value={this.state.message}
onFocus={this.handlerFocus.bind(this)}
onChange={this.handlerChange.bind(this)} />
)
}
}
export default TextArea;En React, <textarea> utilise l'attribut value (comme un <input>), et non un texte entre les balises.
En HTML, une liste de sélection est un <select> qui contient des <option> :
<select>
<option value="1">Element1</option>
<option value="2">Element2</option>
<option value="3">Element3</option>
<option value="4">Element4</option>
<option value="5">Element5</option>
</select>On crée un composant <Select> qui reçoit la prop options (un tableau de chaînes) :
const Select = (props) => {
const handlerChange=(event)=>{
console.log('key='+event.target.value);
}
return (
<select onChange={handlerChange.bind(this)}>
{props.options.map(function(option, index) {
return <option key={index+1} value={index+1}>{option}</option>
})
}
</select>
);
};
export default Select;import React from 'react';
class Select extends React.Component {
constructor(props) {
super(props);
}
handlerChange(event) {
console.log('key='+event.target.value);
}
render() {
return (
<select onChange={this.handlerChange.bind(this)}>
{this.props.options.map(function(option, index) {
return <option key={index+1} value={index+1}>{option}</option>
})
}
</select>
)
}
}
export default Select;<Select options={["Element1", "Element2", "Element3", "Element4", "Element5"]} />À chaque choix, event.target.value donne la valeur de l'option choisie, affichée dans la console : key=3, key=5…
Sélectionner l'élément de valeur 4 au départ
Le support écrit <Select defaultValue={4} />.
<Select defaultValue={4} /> passe defaultValue au composant, mais le composant ne le transmet pas au <select> : rien n'est sélectionné (et il manque la prop options). Il faut le transmettre :
// dans Select.js
<select defaultValue={props.defaultValue} onChange={handlerChange}>
// utilisation
<Select options={["Element1", "Element2", "Element3", "Element4", "Element5"]} defaultValue={4} />
En version contrôlée, on écrit <select value={choix} onChange={e => setChoix(e.target.value)}> : en React, on choisit l'option sélectionnée avec value sur le <select>, pas avec selected sur l'option.
Le composant <RadioGroup> affiche les boutons radio qu'on lui transmet :
const FRadioGroup = (props) => {
return (
<div> {
props.radios.map((radio, index) => {
return (
<label key={index+1}><span>{radio.text}</span>
<input type="radio" value={radio.value} name="radioname" />
</label>
)
}) }
</div>
);
};
export default FRadioGroup;import React from 'react';
class RadioGroup extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div> {
this.props.radios.map((radio, index) => {
return (
<label key={index}><span>{radio.text}</span>
<input type="radio" value={radio.value}
name={radio.name} checked={radio.checked} />
</label>
)
}) }
</div>
)
}
}
export default RadioGroup;import RadioGroup from './RadioGroup';
var radios = [
{ value : 1, text : "radio1" },
{ value : 2, text : "radio2" },
{ value : 3, text : "radio3", checked : true },
{ value : 4, text : "radio4" }
];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<RadioGroup radios={radios} name="group1" />);Les boutons d'un même groupe portent le même name : en choisir un désélectionne le précédent.
Dans la version classe, name={radio.name} lit un name qui n'existe pas dans les objets radios : les boutons ne forment pas un groupe. Le nom du groupe est passé au composant (name="group1") : écris name={this.props.name}. De plus, checked={radio.checked} sans onChange bloque les boutons (champ contrôlé en lecture seule) : utilise defaultChecked={radio.checked} ou gère l'état comme dans le corrigé 9.3.
Plusieurs cases à cocher peuvent être cochées en même temps (contrairement aux boutons radio d'un groupe). On utilise deux composants :
<CheckBox> gère une case, avec this.state.checked ;<CheckBoxGroup> affiche toutes les cases.import React from "react";
class CheckBox extends React.Component {
constructor(props) {
super(props);
this.state = { checked : props.checked || false };
}
handlerChange(event) {
this.setState({checked : event.target.checked});
}
render() {
return (
<label>
<span>{this.props.text}</span>
<input type="checkbox" value={this.props.value}
checked={this.state.checked}
onChange={this.handlerChange.bind(this)} />
<br/>
</label>
)
}
}
class CheckBoxGroup extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>{
this.props.checkboxes.map((checkbox, index) => {
return (
<CheckBox key={index} text={checkbox.text}
value={checkbox.value}
checked={checkbox.checked}
/>
)
})
}
</div>
)
}
}
export default CheckBoxGroup;import CheckBoxGroup from './CheckBoxGroup';
var checkboxes = [
{ value : 1, text : "check1" },
{ value : 2, text : "check2", checked : true },
{ value : 3, text : "check3", checked : true },
{ value : 4, text : "check4" }
];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<CheckBoxGroup checkboxes={checkboxes} />);check2 et check3 sont cochées au départ. Pour une case à cocher, on lit event.target.checked (true ou false), pas value.
Crée un nouveau projet et travaille dans App.js.
1. Les états
name : l'identifiant de l'utilisateur ;errorMessages : un objet avec le message d'erreur et le nom du champ concerné ;isSubmitted : le formulaire a-t-il été envoyé avec succès ?const [name, setName] = useState('');
const [errorMessages, setErrorMessages] = useState({});
const [isSubmitted, setIsSubmitted] = useState(false);2. La fonction qui affiche le message d'erreur d'un champ
const renderErrorMessage = (name) =>
name === errorMessages.name && (
<div className="error">{errorMessages.message}</div>
);3. Le JSX du formulaire
const renderForm = (
<div className="login-form">
<div className="title">Connexion</div>
<div className="form">
<form onSubmit={handleSubmit}>
<div className="input-container">
<label>L'identifiant </label>
<input type="text" name="uname" required />
{renderErrorMessage("uname")}
</div>
<div className="input-container">
<label>Mot de passe </label>
<input type="password" name="upass" required />
{renderErrorMessage("upass")}
</div>
<div className="button-container">
<input type="submit" value="Se connecter" />
</div>
</form>
</div>
</div>
);4. La fonction d'envoi
const database = [
{ username: "user1", password: "pass1" },
{ username: "user2", password: "pass2" }
];
const errors = {
uname: "Votre identifiant est incorrect.",
upass: "Votre mot de passe est incorrect."
};
const handleSubmit = (event) => {
event.preventDefault();
var { uname, upass } = document.forms[0];
setName(uname.value);
const userData = database.find((user) => user.username === uname.value);
if (userData) {
if (userData.password !== upass.value) {
setErrorMessages({ name: "upass", message: errors.upass });
} else {
setIsSubmitted(true);
}
} else {
setErrorMessages({ name: "uname", message: errors.uname });
}
};5. Le message de réussite après l'envoi
return (
<div className="app">
{isSubmitted ? <div><div className="title">Accueil</div><div>Bonjour {name}</div></div> : renderForm}
</div>
);6. Le style (App.css, à importer dans App.js) :
.app {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100vh;
font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif;
background-color: #f8f9fd;
}
.login-form { background-color: white; padding: 2rem; }
input[type="text"],
input[type="password"] {
height: 25px;
width: 250px;
border: 1px solid rgba(0, 0, 0, 0.2);
}
input[type="submit"] {
margin-top: 10px;
cursor: pointer;
font-size: 15px;
background: #01d28e;
border: 1px solid #01d28e;
color: #fff;
padding: 10px 20px;
}
input[type="submit"]:hover { background: #6cf0c2; }
.button-container { display: flex; justify-content: center; }
.list-container { display: flex; }
.error { color: red; font-size: 15px; }
.title { font-size: 30px; margin-bottom: 20px; text-align: center; }
.input-container { display: flex; flex-direction: column; gap: 8px; margin: 10px; }Les quatre affichages attendus : le formulaire, l'erreur d'identifiant, l'erreur de mot de passe, et le message de réussite « Bonjour user1 ».
Dans ce TP, les champs ne sont pas contrôlés : on lit leurs valeurs à l'envoi avec document.forms[0] (chaque champ est accessible par son name). Ça marche, mais la méthode React est le composant contrôlé (un état par champ) ou event.target.elements : refais le TP dans ce style (exercice 9.4).
Crée un formulaire d'inscription avec : le nom (texte), la date de naissance (type="date"), la ville (liste select : Casablanca, Rabat, Marrakech, Tanger, Fès), le genre (boutons radio) et les loisirs (cases à cocher : Sport, Lecture, Musique, Voyage). Tous les champs sont contrôlés. Après l'envoi, affiche le message :
Je suis {name} né le {date_naissance} à {ville} et mes loisirs sont : {loisirs}Réalise le TP 2 (section 8).
Crée le composant fonction Contact avec trois champs contrôlés : email, sujet (select : Information, Réclamation, Autre) et message (textarea). Le bouton Envoyer est désactivé tant que l'email ne contient pas « @ » ou que le message a moins de 10 caractères. À l'envoi, affiche un résumé sous le formulaire.
Réécris RadioGroup en composant fonction contrôlé : il reçoit radios et name, garde la valeur choisie dans l'état (initialisée avec le bouton qui a checked: true) et affiche « Vous avez choisi : radioN » sous les boutons.
Réalise le TP 1, puis refais-le avec deux états contrôlés uname et upass au lieu de document.forms[0].