useState, Immutabilité & Lifting State Up
Utilisez les flèches, cliquez ou glissez pour naviguer
1. Comprendre l'état (state)
Ce qui rend un composant dynamique
2. Utiliser useState avec TypeScript
Le hook fondamental
3. Respecter l'immutabilité
Parallèle avec l'event-sourcing
4. Gérer les événements
click, change, submit
5. Créer des composants contrôlés
Formulaires réactifs
6. Lifting state up
Remonter l'état dans le parent
Rappel : composants statiques vs dynamiques
Comment rendre un composant interactif ?
Introduction à useState
Le hook qui change tout
L'immutabilité et l'event-sourcing
Pourquoi ne jamais muter l'état
Demo live : compteur et formulaire
Composants contrôlés en action
Lifting state up
Partager l'état entre composants
Les composants d'hier sont... figés!
function Greeting() {
return <h1>Bonjour!</h1>;
}
Comment rendre ce composant interactif ?
Comment changer le message en cliquant sur un bouton ?
Une "mémoire" pour le composant
Des données qui changent au fil du temps
Et qui déclenchent un re-render quand elles changent
Un hook qui ajoute de l'état local à un composant fonction
const [count, setCount] = useState(0);
count
La valeur actuelle
Lecture seule!
setCount
La fonction pour modifier
Déclenche un re-render
useState retourne un tuple : [valeur, setter]
// Types simples (inférés)
const [count, setCount] = useState(0); // number
const [name, setName] = useState("Alice"); // string
// Types explicites (pour les objets, tableaux, null)
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Compteur : {count}</p>
<button onClick={() => setCount(count + 1)}>
Incrémenter
</button>
</div>
);
}
Comment ça marche ?
1. count = 0 au départ
2. Clic sur le bouton setCount(count + 1)
3. React re-render avec count = 1
La règle d'or de React
JAMAIS muter l'état directement!
Toujours créer une nouvelle valeur
L'erreur courante
Muter l'état directement
React ne détecte pas le changement!
La bonne pratique
Créer une nouvelle valeur
React détecte et re-render
// Tableaux
items.push(newItem);
// Mauvais! Mute le tableau
setItems([...items, newItem]);
// Bon! Nouveau tableau
// Objets
user.name = "Bob";
// Mauvais! Mute l'objet
setUser({ ...user, name: "Bob" });
// Bon! Nouvel objet
Vous connaissez peut-être ce pattern du backend...
Event-Sourcing
On ne modifie jamais l'état
On ajoute des événements
L'état = réduction des événements
React State
On ne mute jamais l'état
On crée une nouvelle valeur
React compare et re-render
Même philosophie : l'histoire est préservée, pas écrasée
Ajouter à un tableau
[...items, newItem]
Supprimer d'un tableau
items.filter(i => i.id !== id)
Modifier un objet
{ ...user, name: "Bob" }
Modifier dans un tableau
items.map(i => i.id === id ? {...i, done: true} : i)
Ces patterns créent toujours de nouvelles références
Interagir avec l'utilisateur
onClick
Clic
onChange
Saisie
onSubmit
Formulaire
function Button() {
const handleClick = () => {
console.log("Cliqué!");
};
return (
<button onClick={handleClick}>
Clique-moi
</button>
);
}
Note : On passe la fonction, pas son appel!
onClick={handleClick} et non onClick={handleClick()}
function NameInput() {
const [name, setName] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
return (
<input
value={name}
onChange={handleChange}
/>
);
}
Attention : onChange en React vs onchange en HTML
En React, l'événement se déclenche à chaque frappe!
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); // Important!
console.log("Formulaire soumis");
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Envoyer</button>
</form>
);
e.preventDefault() empêche le rechargement de la page
L'input est "contrôlé" par React
La valeur de l'input vient du state
Les changements mettent à jour le state
function EmailForm() {
const [email, setEmail] = useState("");
return (
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
);
}
value={email}
Affiche le state
onChange
Met à jour le state
Re-render
Affiche la nouvelle valeur
State
{email}
=>
Input
value={email}
=>
onChange
setEmail()
=>
State
Nouvelle valeur
Les données circulent dans un seul sens : State => UI => Event => State
interface FormData { name: string; email: string; }
function ContactForm() {
const [form, setForm] = useState<FormData>({ name: "", email: "" });
const updateField = (field: keyof FormData) =>
(e: React.ChangeEvent<HTMLInputElement>) =>
setForm({ ...form, [field]: e.target.value });
return (
<form>
<input value={form.name} onChange={updateField("name")} />
<input value={form.email} onChange={updateField("email")} />
</form>
);
}
Un seul state pour tout le formulaire!
Remonter l'état dans le parent
Quand deux composants enfants ont besoin de partager le même état
Deux enfants ont besoin de la même donnée...
TemperatureInput
state: temperature
BoilingVerdict
a besoin de temperature
Comment partager la température entre les deux?
L'état monte dans le parent commun
Parent
state: temperature
Child A
props: temperature, onTempChange
Child B
props: temperature
Le parent détient l'état, les enfants reçoivent des props
// Parent détient l'état
function Calculator() {
const [temp, setTemp] = useState("");
return (
<div>
<TemperatureInput temperature={temp} onTempChange={setTemp} />
<BoilingVerdict temperature={temp} />
</div>
);
}
// Enfant reçoit props et callback
function TemperatureInput({ temperature, onTempChange }: Props) {
return (
<input
value={temperature}
onChange={(e) => onTempChange(e.target.value)}
/>
);
}
Les erreurs à éviter
Ces erreurs sont fréquentes et compréhensibles
L'erreur
items.push(newItem);
setItems(items);
React ne détecte pas le changement!
Même référence objet
La solution
setItems([...items, newItem]);
Nouveau tableau = nouvelle référence
React détecte et re-render
Rappel : push(), pop(), splice() mutent le tableau!
Problème potentiel
setCount(count + 1);
setCount(count + 1);
Deux appels = +1 seulement!
count est "stale" (obsolète)
La solution
setCount(c => c + 1);
setCount(c => c + 1);
Chaque appel = +1
c est toujours la valeur actuelle
Utilisez le callback quand la nouvelle valeur dépend de l'ancienne
HTML classique
<input
onchange="handleChange()"
/>
Déclenche sur "blur" (perte de focus)
React
<input
onChange={handleChange}
/>
Déclenche à chaque frappe!
En React : onChange (camelCase) et non onchange
À chaque re-render, les fonctions sont recréées!
function Counter() {
// Cette fonction est recréée à chaque render
const handleClick = () => setCount(c => c + 1);
return <button onClick={handleClick}>...</button>;
}
C'est normal! React optimise automatiquement le DOM.
Pas besoin de useCallback pour les cas simples.
useState retourne [valeur, setter]
Le setter déclenche un re-render
JAMAIS muter l'état directement
Toujours créer une nouvelle valeur
Composant contrôlé = valeur dans le state
L'input est "piloté" par React
Lifting state up = partager l'état
Remonter dans le parent commun
useState
[value, setValue] = useState(initial)
Le hook de base pour l'état local
Immutabilité
setItems([...items, newItem])
Jamais de mutation directe
Contrôlé
value={state} + onChange
L'input piloté par React
Lifting
Parent détient, enfants reçoivent
Partager via props
L'état est le coeur de l'interactivité en React!
1. Compteur avec boutons + et -
Utiliser useState avec un nombre
2. Liste de tâches (Todo)
Ajouter/supprimer avec immutabilité
3. Formulaire de contact
Plusieurs champs contrôlés dans un seul state
4. Convertisseur de température
Lifting state up entre Celsius et Fahrenheit
L'état local est la base de React
Prochaine étape : useEffect et les effets de bord