Le hook useState - Séance 3
Le hook useState est le hook le plus important de React. Il permet d'ajouter de la mémoire à vos composants fonctionnels.
Qu'est-ce que le state ?
Le state (état) est la mémoire d'un composant : des données qui peuvent changer au fil du temps et qui déclenchent un nouveau rendu quand elles changent.
import { useState } from 'react';
function Counter() {
// Déclaration d'une variable d'état
const [count, setCount] = useState(0);
return (
<div>
<p>Compteur : {count}</p>
<button onClick={() => setCount(count + 1)}>
Incrémenter
</button>
</div>
);
}
Comment ça marche ?
- Initialisation :
useState(0)crée une variable d'état initialisée à0 - Lecture :
countcontient la valeur actuelle - Modification :
setCount(newValue)met à jour la valeur - Rendu : React ré-affiche le composant avec la nouvelle valeur
Syntaxe de useState
Déstructuration de tableau
const [state, setState] = useState(initialValue);
state: variable contenant la valeur actuellesetState: fonction pour modifier la valeurinitialValue: valeur de départ
Pourquoi des crochets [ ] ?
useState retourne un tableau avec 2 éléments :
const stateArray = useState(0);
// stateArray = [0, function]
// On utilise la déstructuration pour extraire :
const [count, setCount] = useState(0);
// count = 0
// setCount = function
C'est juste du JavaScript ! Vous pouvez nommer les variables comme vous voulez.
Que retourne l'appel à useState(0) ?
Conventions de nommage
// ✅ Convention : [chose, setChose]
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [isOpen, setIsOpen] = useState(false);
const [user, setUser] = useState(null);
// ❌ Évitez des noms incohérents
const [count, updateCount] = useState(0); // Pas terrible
const [x, y] = useState(0); // Pas clair
Valeur initiale
La valeur initiale peut être de n'importe quel type JavaScript.
// Nombre
const [count, setCount] = useState(0);
// String
const [name, setName] = useState('');
// Boolean
const [isVisible, setIsVisible] = useState(false);
// Tableau
const [items, setItems] = useState([]);
// Objet
const [user, setUser] = useState({ name: '', age: 0 });
// Null
const [data, setData] = useState(null);
La valeur initiale n'est utilisée qu'une fois
React utilise la valeur initiale uniquement au premier rendu.
function Counter({ startValue }) {
const [count, setCount] = useState(startValue);
// Si startValue change après, count ne change PAS !
return <p>{count}</p>;
}
Si vous voulez réinitialiser quand une prop change, vous devrez utiliser useEffect (Séance 4).
Mettre à jour l'état
Mise à jour simple
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1); // Nouvelle valeur
};
return <button onClick={increment}>Count: {count}</button>;
}
Mise à jour basée sur l'état précédent
Si la nouvelle valeur dépend de l'ancienne, utilisez une fonction :
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
// ✅ Recommandé : fonction callback
setCount(prevCount => prevCount + 1);
};
return <button onClick={increment}>Count: {count}</button>;
}
Pourquoi utiliser une fonction ?
Problème avec les mises à jour multiples :
// ❌ N'incrémente que de 1 (pas de 3 !)
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
// ✅ Incrémente bien de 3
const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
Règle : Si vous utilisez l'état précédent, passez une fonction !
Si count vaut 5, qu'affichera count après ces 3 appels consécutifs à setCount(count + 1) ?
État avec des objets
Pour mettre à jour un objet, vous devez créer un nouvel objet (immutabilité).
function UserProfile() {
const [user, setUser] = useState({
name: 'Alice',
age: 25,
email: 'alice@example.com'
});
const updateName = (newName) => {
// ❌ FAUX : mutation directe
user.name = newName;
setUser(user);
// ✅ CORRECT : nouveau objet avec spread
setUser({
...user, // Copie toutes les propriétés
name: newName // Remplace name
});
};
return (
<div>
<p>{user.name}</p>
<button onClick={() => updateName('Bob')}>
Changer le nom
</button>
</div>
);
}
Spread operator pour les objets
Le spread (...) copie toutes les propriétés d'un objet :
const user = { name: 'Alice', age: 25 };
const updatedUser = {
...user, // { name: 'Alice', age: 25 }
age: 26 // Remplace age
};
// updatedUser = { name: 'Alice', age: 26 }
Comment mettre à jour correctement le nom d'un utilisateur dans le state ?
État avec des tableaux
Pour mettre à jour un tableau, créez un nouveau tableau.
Ajouter un élément
function TodoList() {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
const newTodo = {
id: Date.now(),
text: text,
completed: false
};
// ✅ Nouveau tableau avec spread
setTodos([...todos, newTodo]);
};
return (
<div>
{todos.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
<button onClick={() => addTodo('Nouvelle tâche')}>
Ajouter
</button>
</div>
);
}
Supprimer un élément
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Apprendre React' },
{ id: 2, text: 'Faire du café' }
]);
const removeTodo = (id) => {
// ✅ filter crée un nouveau tableau
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div>
{todos.map(todo => (
<div key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>
Supprimer
</button>
</div>
))}
</div>
);
}
Modifier un élément
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Apprendre React', completed: false }
]);
const toggleTodo = (id) => {
// ✅ map crée un nouveau tableau
setTodos(todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
));
};
return (
<div>
{todos.map(todo => (
<div key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
</div>
))}
</div>
);
}
N'utilisez PAS les méthodes mutantes
Méthodes à éviter (modifient le tableau original) :
// ❌ INTERDIT
todos.push(newTodo); // Mutation
todos.pop(); // Mutation
todos.splice(index, 1); // Mutation
todos[0] = newValue; // Mutation
// ✅ UTILISEZ
[...todos, newTodo] // Ajout
todos.filter(...) // Suppression
todos.map(...) // Modification
Quelle méthode devez-vous utiliser pour ajouter un élément à un tableau dans le state ?
Plusieurs variables d'état
Vous pouvez appeler useState plusieurs fois dans un même composant :
function RegistrationForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState('');
const [isStudent, setIsStudent] = useState(false);
return (
<form>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<input
value={email}
onChange={e => setEmail(e.target.value)}
/>
<input
type="number"
value={age}
onChange={e => setAge(e.target.value)}
/>
<input
type="checkbox"
checked={isStudent}
onChange={e => setIsStudent(e.target.checked)}
/>
</form>
);
}
Un state ou plusieurs ?
Regroupez si les données sont liées :
// ✅ Bon : données liées
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
// ⚠️ Moins bon : données liées mais séparées
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(0);
// ✅ Bon : données indépendantes
const [user, setUser] = useState({ name: '', email: '' });
const [isMenuOpen, setIsMenuOpen] = useState(false);
Quelle est la meilleure pratique pour organiser plusieurs données liées dans le state ?
Patterns courants
Toggle (basculer)
function Toggle() {
const [isOn, setIsOn] = useState(false);
const toggle = () => setIsOn(!isOn);
return (
<button onClick={toggle}>
{isOn ? 'ON' : 'OFF'}
</button>
);
}
Compteur
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
const reset = () => setCount(0);
return (
<div>
<p>{count}</p>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
<button onClick={increment}>+</button>
</div>
);
}
Input contrôlé
function NameInput() {
const [name, setName] = useState('');
return (
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
placeholder="Votre nom"
/>
);
}
Liste dynamique
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim()) {
setTodos([...todos, { id: Date.now(), text: input }]);
setInput(''); // Réinitialiser l'input
}
};
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}</li>
))}
</ul>
</div>
);
}
Règles importantes
Règles des Hooks
1. Appelez les Hooks au niveau supérieur
// ❌ INTERDIT dans une condition
if (condition) {
const [state, setState] = useState(0);
}
// ❌ INTERDIT dans une boucle
for (let i = 0; i < 5; i++) {
const [state, setState] = useState(0);
}
// ✅ CORRECT au niveau supérieur
function Component() {
const [state, setState] = useState(0);
// ...
}
2. Appelez les Hooks uniquement dans les composants React
// ❌ INTERDIT dans une fonction normale
function normalFunction() {
const [state, setState] = useState(0);
}
// ✅ CORRECT dans un composant
function MyComponent() {
const [state, setState] = useState(0);
}
// ✅ CORRECT dans un custom hook
function useCustomHook() {
const [state, setState] = useState(0);
}
Où pouvez-vous appeler useState ?
Résumé
Ce que vous avez appris
useState en 5 points :
- Déclaration :
const [state, setState] = useState(initialValue) - Lecture : utilisez
statedirectement - Mise à jour : appelez
setState(newValue) - Immutabilité : créez de nouveaux objets/tableaux avec spread
- Fonction callback : utilisez
setState(prev => ...)si vous dépendez de l'ancien état
Prochaine étape : utiliser useState pour créer des formulaires contrôlés !