Indice

Chapitre 1 : useState

Le problème

En JavaScript classique, vous pouvez modifier une variable :

snippet.javascript
let count = 0;
count = count + 1;

Mais en React, cela ne fonctionne pas :

snippet.javascript
function Counter() {
    let count = 0;
 
    const increment = () => {
        count = count + 1;  // La variable change...
        console.log(count); // Affiche bien 1, 2, 3...
    };
 
    // Mais l'UI n'est JAMAIS mise à jour !
    return (
        <div>
            <p>{count}</p>  {/* Affiche toujours 0 */}
            <button onClick={increment}>+1</button>
        </div>
    );
}

Pourquoi ? React ne sait pas que la variable a changé. Il faut lui dire explicitement.

La solution : useState

snippet.javascript
import { useState } from 'react';
 
function Counter() {
    const [count, setCount] = useState(0);
 
    const increment = () => {
        setCount(count + 1);  // Dit à React de re-rendre
    };
 
    return (
        <div>
            <p>{count}</p>
            <button onClick={increment}>+1</button>
        </div>
    );
}

useState retourne un tableau avec :

  1. La valeur actuelle (count)
  2. Une fonction pour la modifier (setCount)

Syntaxe

snippet.javascript
const [state, setState] = useState(initialValue);

Différents types d'état

Nombres

snippet.javascript
const [count, setCount] = useState(0);
setCount(count + 1);
setCount(count - 1);

Chaînes

snippet.javascript
const [name, setName] = useState('');
setName('Jean');
setName(e.target.value);  // Pour un input

Booléens

snippet.javascript
const [isOpen, setIsOpen] = useState(false);
setIsOpen(true);
setIsOpen(!isOpen);  // Toggle

Objets

snippet.javascript
const [user, setUser] = useState({ name: '', email: '' });
 
// INCORRECT - mutation directe
user.name = 'Jean';  // Ne fonctionne pas !
 
// CORRECT - nouvel objet avec spread
setUser({ ...user, name: 'Jean' });

Tableaux

snippet.javascript
const [items, setItems] = useState([]);
 
// Ajouter un élément
setItems([...items, newItem]);
 
// Supprimer un élément
setItems(items.filter(item => item.id !== idToRemove));
 
// Modifier un élément
setItems(items.map(item =>
    item.id === idToUpdate ? { ...item, name: 'Nouveau nom' } : item
));

Mise à jour basée sur l'état précédent

Quand la nouvelle valeur dépend de l'ancienne, utilisez la forme fonctionnelle :

snippet.javascript
// PROBLÈME POTENTIEL avec plusieurs appels rapides
setCount(count + 1);
setCount(count + 1);  // count est toujours l'ancienne valeur !
// Résultat : +1 au lieu de +2
 
// CORRECT - forme fonctionnelle
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
// Résultat : +2

La forme fonctionnelle garantit que vous travaillez avec la dernière valeur.

Plusieurs états dans un composant

snippet.javascript
function LoginForm() {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState(null);
 
    // ...
}

Ou regroupés dans un objet :

snippet.javascript
function LoginForm() {
    const [form, setForm] = useState({
        email: '',
        password: '',
        isLoading: false,
        error: null
    });
 
    const updateField = (field, value) => {
        setForm(prev => ({ ...prev, [field]: value }));
    };
 
    // updateField('email', 'jean@example.com');
}

Initialisation paresseuse

Si la valeur initiale nécessite un calcul coûteux, passez une fonction :

snippet.javascript
// PROBLÈME - calcul exécuté à chaque rendu
const [data, setData] = useState(expensiveCalculation());
 
// CORRECT - calcul exécuté une seule fois
const [data, setData] = useState(() => expensiveCalculation());

Comparaison avec PHP

Concept PHP (session) React (useState)
Stockage $_SESSION['count'] = 0 useState(0)
Lecture $_SESSION['count'] count
Écriture $_SESSION['count']++ setCount(c => c + 1)
Persistance Côté serveur Mémoire navigateur

Exercices

Exercice 1 : Compteur avec min/max

Créer un compteur qui :

Solution :

snippet.javascript
function Counter() {
    const [count, setCount] = useState(0);
 
    const increment = () => {
        setCount(prev => Math.min(prev + 1, 10));
    };
 
    const decrement = () => {
        setCount(prev => Math.max(prev - 1, 0));
    };
 
    return (
        <div>
            <button onClick={decrement} disabled={count === 0}>-</button>
            <span>{count}</span>
            <button onClick={increment} disabled={count === 10}>+</button>
        </div>
    );
}

Exercice 2 : Liste de tâches

Créer un composant qui :

Solution :

snippet.javascript
function TodoList() {
    const [todos, setTodos] = useState([]);
    const [input, setInput] = useState('');
 
    const addTodo = () => {
        if (input.trim()) {
            setTodos([...todos, { id: Date.now(), text: input }]);
            setInput('');
        }
    };
 
    const removeTodo = (id) => {
        setTodos(todos.filter(todo => todo.id !== id));
    };
 
    return (
        <div>
            <input
                value={input}
                onChange={(e) => setInput(e.target.value)}
                onKeyPress={(e) => e.key === 'Enter' && addTodo()}
            />
            <button onClick={addTodo}>Ajouter</button>
 
            <ul>
                {todos.map(todo => (
                    <li key={todo.id}>
                        {todo.text}
                        <button onClick={() => removeTodo(todo.id)}>×</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

Points clés à retenir

  1. useState retourne [valeur, setValeur]
  2. Ne jamais modifier l'état directement, toujours utiliser le setter
  3. Spread operator pour les objets et tableaux : {...obj}, [...arr]
  4. Forme fonctionnelle : setState(prev => ...) quand le nouvel état dépend de l'ancien
  5. L'état est local au composant

← Retour au module | Chapitre suivant : useEffect →