Gestion de l'état local

useState, Immutabilité & Lifting State Up

Utilisez les flèches, cliquez ou glissez pour naviguer

Objectifs de la leçon

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

Plan du cours

1

Rappel : composants statiques vs dynamiques

Comment rendre un composant interactif ?

2

Introduction à useState

Le hook qui change tout

3

L'immutabilité et l'event-sourcing

Pourquoi ne jamais muter l'état

4

Demo live : compteur et formulaire

Composants contrôlés en action

5

Lifting state up

Partager l'état entre composants

Le problème des composants statiques

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 ?

Qu'est-ce que l'état ?

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

useState : Le hook fondamental

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]

useState avec TypeScript

// 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[]>([]);

Demo : Un compteur simple

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

L'Immutabilité

La règle d'or de React

JAMAIS muter l'état directement!

Toujours créer une nouvelle valeur

Pourquoi l'immutabilité ?

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

Parallèle : Event-Sourcing

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

Patterns d'immutabilité

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

Gestion des événements

Interagir avec l'utilisateur

onClick

Clic

onChange

Saisie

onSubmit

Formulaire

onClick : Gérer les clics

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()}

onChange : Gérer les saisies

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!

onSubmit : Gérer les formulaires

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

Composants contrôlés

L'input est "contrôlé" par React

La valeur de l'input vient du state

Les changements mettent à jour le state

Exemple : Input contrôlé

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

Flux de données unidirectionnel

State

{email}

=>

Input

value={email}

=>

onChange

setEmail()

=>

State

Nouvelle valeur

Les données circulent dans un seul sens : State => UI => Event => State

Demo : Formulaire complet

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!

Lifting State Up

Remonter l'état dans le parent

Quand deux composants enfants ont besoin de partager le même état

Le problème

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?

La solution : Remonter l'état

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

Exemple : Calculateur de température

// 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)}

/>

);

}

Pièges courants

Les erreurs à éviter

Ces erreurs sont fréquentes et compréhensibles

Piège 1 : Muter un tableau

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!

Piège 2 : Oublier le callback du setter

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

Piège 3 : Confusion onChange

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

Piège 4 : Les fonctions recréées

À 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.

Points clés à retenir

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

À retenir!

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!

Exercices pratiques

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

Questions?

L'état local est la base de React

Prochaine étape : useEffect et les effets de bord