Développement Web
React
Bibliothèque UI
Composants, hooks, état, effets, contexte, routing et patterns essentiels pour construire des interfaces modernes avec React 18+.
01 — Fondamentaux
Qu'est-ce que React ?
⚛
React est une bibliothèque JavaScript (pas un framework) créée par Meta pour construire des interfaces utilisateur. Son principe fondateur : l'UI est une fonction de l'état — UI = f(state). Quand l'état change, React recalcule et met à jour uniquement ce qui a changé dans le DOM.
<App />racine de l'application
┣ <Navbar />composant de navigation
┣ <HomePage />page courante
┃ ┣ <HeroSection />
┃ ┣ <CardList />
┃ ┃ ┗ <Card /> × Nréutilisable
┗ <Footer />
Créer un projet React (Vite)
# Créer un projet avec Vite (recommandé)
npm create vite@latest mon-app -- --template react
cd mon-app
npm install
npm run dev # → http://localhost:5173
# Ou avec Create React App (legacy)
npx create-react-app mon-app
cd mon-app
npm start
# Structure d'un projet Vite+React :
mon-app/
├── public/ ← fichiers statiques
├── src/
│ ├── main.jsx ← point d'entrée
│ ├── App.jsx ← composant racine
│ ├── components/ ← composants réutilisables
│ ├── pages/ ← pages/routes
│ ├── hooks/ ← hooks personnalisés
│ └── assets/ ← images, fonts
├── index.html
└── vite.config.js
02
JSX
Syntaxe JSX
// JSX = JavaScript + XML-like syntax
// Transpilé en React.createElement() par Babel/Vite
// ✅ JSX valide
const element = (
<div className="card">
<h1>{titre}</h1> // {} = expression JS
<p>{2 + 2}</p> // → 4
<p>{user.name}</p>
<img src={url} alt="photo" />
</div>
);
// Différences JSX vs HTML :
class → className
for → htmlFor
onclick → onClick (camelCase)
style="..." → style={{ color: 'red' }}
// Fragment — éviter un div inutile
return (
<>
<h1>Titre</h1>
<p>Paragraphe</p>
</>
);
// Expressions JS dans JSX
<p>{isAdmin ? 'Admin' : 'Visiteur'}</p>
<div style={{ color: actif ? 'green' : 'red' }}>
Règles JSX importantes
// 1. Un seul élément racine (ou Fragment)
// ✗ Incorrect
return (
<h1>Titre</h1>
<p>Texte</p> // ← erreur !
);
// ✅ Correct — Fragment
return (
<>
<h1>Titre</h1>
<p>Texte</p>
</>
);
// 2. Les balises doivent être fermées
<input /> // ✅ self-closing obligatoire
<br />
<img src="..." />
// 3. Les commentaires JSX
{/* Ceci est un commentaire JSX */}
// 4. Style inline = objet JS double {{
<div style={{ fontSize: '16px', marginTop: '1rem' }}>
// 5. className pour les classes CSS
<div className="container active">
// 6. Spread props
const props = { id: 'main', role: 'main' };
<div {...props}>
03
Composants
Composant fonctionnel
// Composant = fonction qui retourne du JSX
// Nom commence par une MAJUSCULE obligatoire
function Bouton({ label, onClick, variant = 'primary' }) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{label}
</button>
);
}
// Utilisation
<Bouton label="Valider" onClick={handleSubmit} />
<Bouton label="Annuler" variant="danger" />
// Arrow function (aussi valide)
const Card = ({ titre, description, children }) => (
<div className="card">
<h2>{titre}</h2>
<p>{description}</p>
{children} // ← contenu fils
</div>
);
// Utiliser children
<Card titre="Mon titre">
<p>Contenu personnalisé</p>
</Card>
Props & PropTypes / TypeScript
// Typage avec TypeScript (recommandé)
interface CardProps {
titre: string;
description: string;
image?: string; // optionnel
onClick?: () => void;
children?: React.ReactNode;
}
function Card({ titre, description, image, onClick, children }: CardProps) {
return (
<div className="card" onClick={onClick}>
{image && <img src={image} alt={titre} />}
<h2>{titre}</h2>
<p>{description}</p>
{children}
</div>
);
}
// Règles d'or des composants :
// ✅ Pures — même props → même résultat
// ✅ Ne pas modifier les props reçues
// ✅ Un composant = une responsabilité
// ✅ Nommer clairement (nom du rôle)
// ✅ Extraire si > ~100 lignes
04
Props — passage de données
Passer & recevoir des props
// Props = propriétés passées à un composant enfant
// Sens unique : parent → enfant (unidirectionnel)
// Parent
function ParentPage() {
const user = { nom: 'Alice', age: 28 };
return <ProfilCard user={user} onEdit={handleEdit} />;
}
// Enfant — destructuring des props
function ProfilCard({ user, onEdit }) {
return (
<div>
<p>{user.nom}, {user.age} ans</p>
<button onClick={onEdit}>Modifier</button>
</div>
);
}
// Valeurs par défaut
function Badge({ label, color = 'blue', size = 'md' }) { ... }
// Spread props — transmettre tout
function InputCustom({ label, ...rest }) {
return (
<label>
{label}
<input {...rest} /> // tout le reste va à input
</label>
);
}
Communication enfant → parent (callbacks)
// Pour remonter de l'info vers le parent :
// passer une fonction callback en prop
function Parent() {
const [message, setMessage] = useState('');
const handleMessageFromChild = (msg) => {
setMessage(msg); // ← reçoit depuis l'enfant
};
return (
<>
<p>Message reçu : {message}</p>
<Enfant onEnvoi={handleMessageFromChild} />
</>
);
}
function Enfant({ onEnvoi }) {
return (
<button onClick={() => onEnvoi('Bonjour !')}>
Envoyer au parent
</button>
);
}
// Règle : les données descendent (props)
// les événements remontent (callbacks)
05 — État & Effets
useState — l'état local
useState — bases
import { useState } from 'react';
function Compteur() {
// [valeur, fonctionMàJ] = useState(valeurInitiale)
const [count, setCount] = useState(0);
return (
<div>
<p>Compteur : {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(c => c - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// État avec objet
const [user, setUser] = useState({ nom: '', email: '' });
// ✅ Toujours créer un nouveau objet (immutabilité)
setUser({ ...user, nom: 'Alice' });
// ✗ Modifier directement = bug silencieux
user.nom = 'Alice'; // ← React ne re-render pas !
setUser(user);
// État avec tableau
const [items, setItems] = useState([]);
setItems([...items, newItem]); // ajouter
setItems(items.filter(i => i.id !== id)); // supprimer
setItems(items.map(i => i.id === id ? {...i, done: true} : i)); // modifier
useState — pièges courants
// 1. Mise à jour asynchrone — utiliser la forme fonctionnelle
function incrementTrois() {
// ✗ count est capturé à la valeur initiale
setCount(count + 1);
setCount(count + 1);
setCount(count + 1); // → count + 1 seulement !
// ✅ forme fonctionnelle — reçoit la valeur à jour
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1); // → count + 3 ✓
}
// 2. Initialisation coûteuse — lazy initializer
const [data, setData] = useState(() => {
return JSON.parse(localStorage.getItem('data')) || [];
});
// La fonction n'est appelée qu'une seule fois
// 3. Regrouper les états liés avec useReducer
// Si plusieurs états évoluent toujours ensemble :
const [form, setForm] = useState({
nom: '', email: '', age: ''
});
// Mise à jour partielle avec spread
setForm(f => ({ ...f, nom: 'Alice' }));
06
useEffect — effets de bord
MountuseEffect(() => {…}, [])
UpdateuseEffect(() => {…}, [dep])
Unmountreturn () => cleanup()
useEffect — patterns
import { useState, useEffect } from 'react';
// 1. Exécuter à chaque render (rarement utile)
useEffect(() => { console.log('render'); });
// 2. Exécuter une seule fois au montage
useEffect(() => {
fetchUserData();
}, []); // ← tableau vide = montage seulement
// 3. Exécuter quand une dépendance change
useEffect(() => {
document.title = `Résultats pour "${query}"`;
}, [query]); // ← relancé à chaque changement de query
// 4. Fetch de données avec cleanup
useEffect(() => {
let cancelled = false;
async function fetchData() {
const res = await fetch(`/api/users/${id}`);
const data = await res.json();
if (!cancelled) setUser(data);
}
fetchData();
return () => { cancelled = true; }; // cleanup
}, [id]);
// 5. Abonnement / désabonnement
useEffect(() => {
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
Pièges useEffect
// ⚠ Dépendances manquantes → comportement inattendu
// Le linter ESLint (react-hooks/exhaustive-deps) détecte ça
// ✗ count manquant dans les dépendances
useEffect(() => {
console.log(count); // count est toujours 0 !
}, []);
// ✅ Ajouter la dépendance
useEffect(() => {
console.log(count);
}, [count]);
// ⚠ Objet/tableau en dépendance → boucle infinie
// ✗ Boucle infinie (options recréé à chaque render)
const options = { limit: 10 };
useEffect(() => { fetchData(options); }, [options]);
// ✅ Utiliser useMemo ou déplacer la déclaration
const options = useMemo(() => ({ limit: 10 }), []);
// ⚠ async directement dans useEffect
// ✗ Incorrect
useEffect(async () => { await fetch(...) }, []);
// ✅ Correct — fonction async à l'intérieur
useEffect(() => {
const load = async () => { await fetch(...) };
load();
}, []);
07
useRef, useMemo & useCallback
useRef — référence sans re-render
import { useRef } from 'react';
// 1. Référencer un élément DOM
function SearchInput() {
const inputRef = useRef(null);
const focusInput = () => inputRef.current.focus();
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus</button>
</>
);
}
// 2. Stocker une valeur sans déclencher de re-render
function Timer() {
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => {...}, 1000);
};
const stop = () => {
clearInterval(intervalRef.current);
};
return <><button onClick={start}>Start</button>
<button onClick={stop}>Stop</button></>;
}
// 3. Valeur précédente
function usePrevious(value) {
const ref = useRef();
useEffect(() => { ref.current = value; }, [value]);
return ref.current;
}
useMemo & useCallback
import { useMemo, useCallback } from 'react';
// useMemo — mémoïser un calcul coûteux
function ListeFiltrée({ items, filtre }) {
// Recalcule seulement si items ou filtre change
const itemsFiltres = useMemo(() => {
console.log('Calcul du filtre...');
return items.filter(i => i.nom.includes(filtre));
}, [items, filtre]);
return <ul>{itemsFiltres.map(i => <li>{i.nom}</li>)}</ul>;
}
// useCallback — mémoïser une fonction
// Évite de recréer la fonction à chaque render
function Parent() {
const [count, setCount] = useState(0);
// ✅ handleClick stable entre les renders
const handleClick = useCallback(() => {
console.log('cliqué');
}, []); // ← ne dépend de rien → jamais recréé
return <EnfantMemoise onClick={handleClick} />;
}
// React.memo — éviter les re-renders inutiles
const EnfantMemoise = React.memo(function Enfant({ onClick }) {
console.log('Render Enfant'); // seulement si onClick change
return <button onClick={onClick}>Cliquer</button>;
});
08
useContext — état partagé
Créer et utiliser un contexte
// 1. Créer le contexte
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext('light');
// 2. Provider — fournir la valeur
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Navbar />
<Main />
</ThemeContext.Provider>
);
}
// 3. Consumer — utiliser depuis n'importe quel enfant
function BoutonTheme() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Thème actuel : {theme}
</button>
);
}
// Pas besoin de passer theme et setTheme via les props !
Contexte avec hook personnalisé
// Pattern recommandé — encapsuler dans un hook
// auth-context.jsx
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = async (email, password) => {
const data = await authApi.login(email, password);
setUser(data.user);
};
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
// Hook custom — plus pratique que useContext direct
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth hors AuthProvider');
return ctx;
}
// Utilisation dans un composant :
const { user, login, logout } = useAuth();
09
Hooks personnalisés
Hooks utiles à créer
// useFetch — fetch de données générique
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(url)
.then(r => r.json())
.then(d => { if (!cancelled) { setData(d); setLoading(false); } })
.catch(e => { if (!cancelled) { setError(e); setLoading(false); } });
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
// Utilisation
function UserList() {
const { data, loading, error } = useFetch('/api/users');
if (loading) return <Spinner />;
if (error) return <ErrorMsg error={error} />;
return <ul>{data.map(u => <li>{u.name}</li>)}</ul>;
}
useLocalStorage & useDebounce
// useLocalStorage
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch { return initialValue; }
});
const setValue = (value) => {
setStoredValue(value);
localStorage.setItem(key, JSON.stringify(value));
};
return [storedValue, setValue];
}
// useDebounce — délai avant d'appliquer une valeur
function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// useDebounce pour la recherche
function Search() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 500);
useEffect(() => {
if (debouncedQuery) searchApi(debouncedQuery);
}, [debouncedQuery]); // API appelée 500ms après la saisie
}
10 — Patterns
Listes & clés
Rendu de listes avec .map()
const produits = [
{ id: 1, nom: 'Clavier', prix: 45 },
{ id: 2, nom: 'Souris', prix: 25 },
{ id: 3, nom: 'Écran', prix: 320 },
];
function ListeProduits() {
return (
<ul>
{produits.map(produit => (
<li key={produit.id}> // ← key OBLIGATOIRE
{produit.nom} — {produit.prix} €
</li>
))}
</ul>
);
}
// ✅ key = identifiant stable et unique
// ✗ key={index} → bug si liste réorganisée
// Composant pour chaque item
function ListeProduits({ produits }) {
return (
<ul>
{produits.map(p => (
<ProduitItem key={p.id} produit={p} />
))}
</ul>
);
}
function ProduitItem({ produit }) {
return <li>{produit.nom} — {produit.prix} €</li>;
}
Rendu conditionnel
// 1. Opérateur ternaire
{isLoggedIn
? <Dashboard />
: <LoginPage />
}
// 2. Court-circuit &&
{isAdmin && <AdminPanel />}
{errors.length > 0 && <ErrorList errors={errors} />}
// 3. If précoce (guard clause)
function UserProfile({ user }) {
if (!user) return <p>Chargement…</p>;
if (user.error) return <ErrorPage />;
return <div>{user.nom}</div>;
}
// 4. Switch / objet lookup
const composants = {
loading: () => <Spinner />,
error: () => <ErrorMsg />,
success: () => <DataView />,
};
const Comp = composants[status];
return <Comp />;
// 5. Retourner null = ne rien afficher
function Alerte({ message }) {
if (!message) return null;
return <div className="alerte">{message}</div>;
}
11
Formulaires contrôlés
Formulaire contrôlé
function FormulaireContact() {
const [form, setForm] = useState({
nom: '', email: '', message: ''
});
const [errors, setErrors] = useState({});
// Handler générique — utilise le name de l'input
const handleChange = (e) => {
const { name, value } = e.target;
setForm(f => ({ ...f, [name]: value }));
};
const validate = () => {
const errs = {};
if (!form.nom) errs.nom = 'Nom requis';
if (!form.email.includes('@')) errs.email = 'Email invalide';
return errs;
};
const handleSubmit = (e) => {
e.preventDefault();
const errs = validate();
if (Object.keys(errs).length) { setErrors(errs); return; }
submitApi(form);
};
return (
<form onSubmit={handleSubmit}>
<input name="nom" value={form.nom} onChange={handleChange} />
{errors.nom && <span>{errors.nom}</span>}
<input name="email" value={form.email} onChange={handleChange} />
<button type="submit">Envoyer</button>
</form>
);
}
React Hook Form (bibliothèque)
// npm install react-hook-form
// Beaucoup moins de boilerplate, meilleures perfs
import { useForm } from 'react-hook-form';
function FormulaireRHF() {
const {
register, handleSubmit,
formState: { errors }
} = useForm();
const onSubmit = (data) => {
console.log(data); // { nom: '...', email: '...' }
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('nom', {
required: 'Nom requis',
minLength: { value: 2, message: 'Trop court' }
})}
/>
{errors.nom && <p>{errors.nom.message}</p>}
<input
{...register('email', {
required: 'Email requis',
pattern: { value: /^[^@]+@[^@]+$/, message: 'Email invalide' }
})}
/>
{errors.email && <p>{errors.email.message}</p>}
<button type="submit">Envoyer</button>
</form>
);
}
12
Lifting state up
Partager l'état entre frères
// Problème : deux composants frères ont besoin
// du même état → le remonter au parent commun
// ✅ État dans le parent commun
function ConverterApp() {
const [celsius, setCelsius] = useState('');
const fahrenheit = celsius ? celsius * 9/5 + 32 : '';
return (
<>
<InputTemp
label="Celsius"
value={celsius}
onChange={setCelsius}
/>
<InputTemp
label="Fahrenheit"
value={fahrenheit}
onChange={f => setCelsius((f - 32) * 5/9)}
/>
</>
);
}
function InputTemp({ label, value, onChange }) {
return (
<label>
{label} :
<input
value={value}
onChange={e => onChange(e.target.value)}
/>
</label>
);
}
useReducer — état complexe
import { useReducer } from 'react';
const initialState = { items: [], total: 0 };
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
return {
items: [...state.items, action.item],
total: state.total + action.item.prix
};
case 'REMOVE_ITEM':
const item = state.items.find(i => i.id === action.id);
return {
items: state.items.filter(i => i.id !== action.id),
total: state.total - item.prix
};
case 'CLEAR':
return initialState;
default:
return state;
}
}
function Panier() {
const [cart, dispatch] = useReducer(cartReducer, initialState);
return (
<>
<button onClick={() => dispatch({ type: 'CLEAR' })}>
Vider ({cart.items.length})
</button>
</>
);
}
13 — Écosystème
React Router v6
Configuration des routes
// npm install react-router-dom
// main.jsx
import { BrowserRouter } from 'react-router-dom';
createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
// App.jsx
import { Routes, Route, Navigate } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/users" element={<UsersPage />} />
<Route path="/users/:id" element={<UserDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>
);
}
// Routes imbriquées (layout)
<Route path="/dashboard" element={<DashLayout />}>
<Route index element={<DashHome />} />
<Route path="stats" element={<Stats />} />
<Route path="users" element={<Users />} />
</Route>
Navigation & hooks router
import { Link, NavLink, useNavigate,
useParams, useSearchParams } from 'react-router-dom';
// Liens
<Link to="/about">À propos</Link>
<NavLink // ajoute class "active"
to="/dashboard"
className={({isActive}) => isActive ? 'active' : ''}
>Dashboard</NavLink>
// Navigation programmée
const navigate = useNavigate();
navigate('/home');
navigate(-1); // retour arrière
navigate('/login', { replace: true }); // remplace l'historique
// Paramètres d'URL (/users/:id)
const { id } = useParams();
// URL /users/42 → id = "42"
// Query strings (/search?q=react)
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q'); // "react"
setSearchParams({ q: 'angular' }); // mise à jour URL
// Outlet — rendu des routes enfants
import { Outlet } from 'react-router-dom';
function DashLayout() {
return <><Sidebar /><Outlet /></>;
}
14
Gestion d'état globale
Zustand — le plus simple
// npm install zustand
import { create } from 'zustand';
// Créer le store
const useCartStore = create((set, get) => ({
items: [],
total: 0,
addItem: (item) => set(state => ({
items: [...state.items, item],
total: state.total + item.prix,
})),
removeItem: (id) => {
const item = get().items.find(i => i.id === id);
set(state => ({
items: state.items.filter(i => i.id !== id),
total: state.total - item.prix,
}));
},
clear: () => set({ items: [], total: 0 }),
}));
// Utiliser dans n'importe quel composant
function CartIcon() {
const { items, clear } = useCartStore();
return <button onClick={clear}>🛒 {items.length}</button>;
}
| Solution | Quand l'utiliser | Complexité |
|---|---|---|
| useState | État local d'un composant | ⭐ |
| useContext | Données partagées simples (thème, user) | ⭐⭐ |
| useReducer | État complexe avec transitions définies | ⭐⭐ |
| Zustand | État global simple, peu de boilerplate | ⭐⭐ |
| Jotai | État atomique, performances optimales | ⭐⭐ |
| Redux Toolkit | Large app, besoin de devtools avancés | ⭐⭐⭐ |
| TanStack Query | État serveur (cache, fetch, mutations) | ⭐⭐ |
💡
Règle : ne pas sur-ingénier. Commencer par useState + useContext. Ajouter Zustand ou TanStack Query si les besoins le justifient. Redux seulement pour les très grandes applications.
15
Performance
Lazy loading & Suspense
import { lazy, Suspense } from 'react';
// Charger un composant de manière paresseuse
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
// Le bundle Dashboard n'est chargé que quand
// l'utilisateur navigue vers /dashboard
// Error Boundary — attraper les erreurs de rendu
import { ErrorBoundary } from 'react-error-boundary';
<ErrorBoundary fallback={<ErrorPage />}>
<MonComposant />
</ErrorBoundary>
Optimisations clés
// 1. React.memo — éviter les re-renders inutiles
const ListItem = React.memo(({ item, onDelete }) => (
<li>{item.nom} <button onClick={() => onDelete(item.id)}>✕</button></li>
));
// 2. useCallback pour les handlers passés à memo
const handleDelete = useCallback((id) => {
setItems(items => items.filter(i => i.id !== id));
}, []); // ← stable, ListItem ne re-rend pas inutilement
// 3. Virtualisation pour les grandes listes
// npm install @tanstack/react-virtual
import { useVirtualizer } from '@tanstack/react-virtual';
// Affiche seulement les éléments visibles (100k lignes = ok)
// 4. Code splitting — importer seulement ce dont on a besoin
import debounce from 'lodash/debounce'; // ✅ tree-shakeable
import _ from 'lodash'; // ✗ bundle entier
// 5. Profiler React DevTools
// Installer l'extension navigateur "React DevTools"
// Onglet Profiler → enregistrer → voir qui re-render
16 — Référence
Cheat sheet React
Hooks essentiels
| useState(init) | État local, re-render au changement |
| useEffect(fn, deps) | Effets de bord après le render |
| useRef(init) | Référence DOM ou valeur stable |
| useContext(ctx) | Lire un contexte |
| useMemo(fn, deps) | Calcul mémoïsé |
| useCallback(fn, deps) | Fonction mémoïsée |
Règles des hooks
| ✅ Au top level | Jamais dans if/for/fonctions |
| ✅ Dans composants React | Ou dans des hooks custom |
| Préfixe use* | Obligatoire pour les hooks custom |
| Dépendances exhaustives | Linter eslint-plugin-react-hooks |
État — immutabilité
| Objet | setObj({ ...obj, key: val }) |
| Ajouter | setArr([...arr, item]) |
| Supprimer | setArr(arr.filter(...)) |
| Modifier | setArr(arr.map(...)) |
| Forme fn | setState(s => s + 1) |
Écosystème
| React Router | Navigation SPA |
| TanStack Query | Fetch & cache serveur |
| Zustand | État global simple |
| React Hook Form | Formulaires performants |
| Zod | Validation de schémas |