⚛️ React – Installation, Hooks & Déploiement
Guide complet IDEO‑Lab pour la bibliothèque UI JavaScript (Hooks, VDOM, JSX, Routage).
Vue d'ensemble
Bibliothèque UI, Virtual DOM, Déclaratif.
Bibliothèque UI VDOMJSX (Syntaxe)
HTML dans JavaScript, className, {}.
Installation (Vite)
npm create vite@latest (moderne), CRA (ancien).
Composants (Function)
Composants fonctionnels (vs Classes).
Function ComposantProps (Propriétés)
Passer des données (Parent -> Enfant).
Props DestructuringState (useState)
Gérer l'état local, useState, immutabilité.
Lifecycle (useEffect)
Effets de bord, data fetching, cleanup.
useEffect LifecycleÉvénements & Formulaires
onClick, onChange, "Controlled Components".
Rendu (Listes & Conditions)
.map() (key prop), && (ternaire).
Data Fetching (API)
fetch/Axios dans useEffect, React Query.
Routage (React Router)
BrowserRouter, Routes, Route, Link.
State: useContext
createContext, Provider, useContext.
State Global (Avancé)
Redux (Toolkit) & Zustand.
Redux ZustandBuild & Déploiement
npm run build, Vercel/Netlify, Nginx.
Cheat-sheet (Hooks)
useState, useEffect, useContext, useRef.
Paramètres du projet
Commandes générées
Copie/colle tout le bloc. Ajuste si nécessaire (ex: Node version, proxy API, etc.).
Clique sur “Générer” 👈
Snippet optionnel : Proxy API (dev)
Si ton backend est sur http://localhost:8000, tu peux proxyfier via Vite pour éviter CORS en dev.
/* vite.config.js (extrait)
export default defineConfig({
server: {
proxy: {
"/api": "http://localhost:8000"
}
}
});
*/1) Routing (SPA & navigation)
Quand ton app dépasse 1 page, tu as besoin d’un routeur côté client (URLs propres, nested routes, layouts, params…).
| Lib | Pourquoi c’est populaire | Quand l’utiliser | Install |
|---|---|---|---|
| react-router-dom | Standard de facto, API v6 solide | SPA classique | npm i react-router-dom |
| @tanstack/router | Router typé, moderne, proche TanStack | Apps TS exigeantes | npm i @tanstack/router |
// Exemple "minimum"
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";2) Data fetching, cache & “server state”
En prod, gérer fetch/loading/error/cache à la main est coûteux. Les libs de “server state” sont un énorme gain de productivité.
| Lib | Points forts | Cas idéal | Install |
|---|---|---|---|
| @tanstack/react-query | Cache, refetch, pagination, mutations | Standard moderne | npm i @tanstack/react-query |
| SWR | Très simple, “stale-while-revalidate” | Apps Next.js / simple data | npm i swr |
| Axios | Interceptors, ergonomie HTTP | Auth headers/refresh | npm i axios |
3) State global (client state)
Quand l’état est partagé partout (UI state, session, prefs) : un store évite les props à rallonge et structure la logique.
| Lib | Style | Pourquoi populaire | Install |
|---|---|---|---|
| @reduxjs/toolkit | Enterprise | Prévisible, DevTools, conventions | npm i @reduxjs/toolkit react-redux |
| Zustand | Minimaliste | Très léger, API hooks, selectors | npm i zustand |
| Jotai | Atoms | Granularité fine, simple | npm i jotai |
| Recoil | Atoms/graph | Historique, concept “graph state” | npm i recoil |
// Zustand (extrait)
import { create } from "zustand";
export const useStore = create(set => ({ count: 0, inc: () => set(s => ({count: s.count+1})) }));4) Forms, validation & UX
Les formulaires sont un gros sujet (perf, validation, erreurs, schema, champs dynamiques). Les libs spécialisées font gagner énormément de temps.
| Lib | Points forts | Stack “classique” | Install |
|---|---|---|---|
| react-hook-form | Perf, uncontrolled-friendly, simple | RHF + Zod/Yup | npm i react-hook-form |
| Formik | Historique, stable | Legacy / équipes habituées | npm i formik |
| Zod | Schema TS-first | Validation + parsing | npm i zod |
| Yup | Schema validation | Formik-friendly | npm i yup |
// RHF + Zod (mini)
import { useForm } from "react-hook-form";
import { z } from "zod";5) UI kits, composants & styling
Pour livrer vite : UI kits + design system. Pour custom : Tailwind + primitives.
| Lib | Rôle | Pourquoi populaire | Install |
|---|---|---|---|
| Tailwind CSS | Utility-first CSS | Rapide, cohérent, DX | npm i -D tailwindcss |
| MUI (Material UI) | UI kit complet | Enterprise, composants riches | npm i @mui/material |
| Ant Design | UI kit | Dashboards, tables, composants | npm i antd |
| Chakra UI | UI kit | API simple, accessible | npm i @chakra-ui/react |
| shadcn/ui | Composants “copy-paste” | Basé Radix, très moderne | npx shadcn-ui@latest init |
| Radix UI | Primitives accessibles | Base solide pour design system | npm i @radix-ui/react-* |
| Framer Motion | Animations | Très puissant, simple | npm i framer-motion |
6) Tables, charts & virtualisation
Le “pain” en dashboard : tables, tri, filtres, pagination, charts, performance listes.
| Lib | Usage | Point fort | Install |
|---|---|---|---|
| @tanstack/react-table | Tables headless | Tri, filtres, pagination, perf | npm i @tanstack/react-table |
| AG Grid | Table enterprise | Features massives | npm i ag-grid-react |
| Recharts | Charts | Simple, efficace | npm i recharts |
| Chart.js | Charts | Large écosystème | npm i chart.js |
| react-window | Virtualisation | Listes très longues | npm i react-window |
| @tanstack/react-virtual | Virtualisation | Très moderne, flexible | npm i @tanstack/react-virtual |
// Virtualisation (idée)
import { FixedSizeList as List } from "react-window";7) Tooling, tests, qualité & dev experience
| Catégorie | Lib / outil | Pourquoi important | Install |
|---|---|---|---|
| Build | Vite | Dev server rapide + build moderne | npm create vite@latest |
| Lint | ESLint | Qualité code, règles React | npm i -D eslint |
| Format | Prettier | Style cohérent, reviews rapides | npm i -D prettier |
| Tests unit | Vitest | Rapide, intégré Vite | npm i -D vitest |
| Tests UI | Testing Library | Test “comme l’utilisateur” | npm i -D @testing-library/react |
| E2E | Playwright | Tests navigateur modernes | npm i -D @playwright/test |
| Qualité | TypeScript | Réduit bugs, meilleure DX | npm i -D typescript |
| Errors | Sentry | Monitoring prod | npm i @sentry/react |
8) Auth, i18n, analytics, misc (plugins très fréquents)
| Besoin | Lib populaire | Pourquoi | Install |
|---|---|---|---|
| i18n | react-i18next | Standard i18n, namespaces | npm i i18next react-i18next |
| Auth | Auth0 React SDK / Firebase Auth | Login, tokens, sessions | npm i @auth0/auth0-react |
| Dates | date-fns | Léger, utilitaire | npm i date-fns |
| Icons | lucide-react / react-icons | DX, cohérence | npm i lucide-react |
| Notifications | react-hot-toast | Toasts élégants | npm i react-hot-toast |
| Analytics | Plausible / GA wrapper | Mesure usage | npm i plausible-tracker |
| SEO (SPA) | react-helmet-async | Meta tags dynamiques | npm i react-helmet-async |
Sélection “starter pro” (stack recommandée)
Core:
- react-router-dom
- @tanstack/react-query
- zustand (ou @reduxjs/toolkit)
- react-hook-form + zod
UI:
- tailwindcss
- shadcn/ui (+ radix)
- framer-motion
Quality:
- typescript
- eslint + prettier
- vitest + testing-library
- playwright
- sentryPhrase à retenir
“Choisis une lib par problème (routing, server-state, forms),
pas une lib parce qu’elle est à la mode.”React : la promesse
React est une bibliothèque UI centrée sur un principe simple : l’interface est une fonction de l’état. Tu écris des composants (fonctions) qui retournent une UI (JSX) en fonction des props et du state. Quand l’état change, React calcule la différence et met à jour le DOM de façon contrôlée.
Ce que React fait bien
- Composition : assembler une UI complexe avec des briques simples.
- State management local : rendre la UI prédictible (au lieu de manipuler le DOM à la main).
- Écosystème : routing, data fetching, forms, state global, SSR, tooling…
- Performance pratique : updates ciblées + scheduling moderne (selon versions).
Ce que React ne fait pas “tout seul”
- Router (React Router / Next Router…)
- Data fetching (fetch/axios, TanStack Query, loaders…)
- State global (Redux, Zustand, Jotai, Context+Reducer…)
- Build tool (Vite, Next.js, etc.)
Équation mentale
UI = f(props, state)
- props : données "entrantes" (parent → enfant), immuables côté enfant
- state : données "locales" (interactions), modifiables via setState/useState
- render : recalcul de l'arbre UI
- commit : application minimale des changements au DOM réelDéclaratif vs Impératif : pourquoi ça change tout
En mode impératif, tu “pilotes” le DOM : tu cibles des éléments et tu les modifies. En mode déclaratif (React), tu décris l’UI attendue pour un état donné ; React s’occupe des mises à jour.
Impératif (style jQuery / DOM API)
// "Trouve la div, change le texte, cache le bouton, ..."
// Risque : état dispersé et incohérent ("UI drift")
const el = document.getElementById("count");
el.textContent = String(count);
if(count > 3){
document.getElementById("cta").style.display = "none";
}Problème typique : tu finis avec un état “réel” dans le DOM + un état “logique” dans le JS, et ils divergent avec le temps.
Déclaratif (React)
function Counter(){
const [count, setCount] = React.useState(0);
return (
<div>
<p id="count">{count}</p>
{count <= 3 && (
<button id="cta" onClick={() => setCount(c => c + 1)}>
+1
</button>
)}
</div>
);
}Ici, l’UI est toujours cohérente avec count : un seul “source of truth”.
3 bénéfices majeurs
- Lisibilité : on comprend l’UI en lisant le composant.
- Testabilité : on teste des sorties (UI) pour des entrées (state/props).
- Robustesse : moins de logique DOM “au fil de l’eau”.
Composants : fonctions, état, composition
Un composant React est (souvent) une fonction pure-ish : elle lit props + state, et retourne une description d’UI. La puissance vient de la composition (petites briques).
Props : contrat d’API d’un composant
function PriceTag({ amount, currency = "EUR" }){
return <strong>{amount} {currency}</strong>;
}
// parent:
<PriceTag amount={29.9} currency="EUR" />- Les props descendent (parent → enfant).
- Un enfant ne modifie pas ses props.
State : interaction & dynamique locale
function SearchBox(){
const [q, setQ] = React.useState("");
return (
<input
value={q}
onChange={(e) => setQ(e.target.value)}
placeholder="Search..."
/>
);
}- State = “mémoire” du composant.
- On met à jour via setters (
setQ), pas via mutation brute.
Composition (pattern clé)
function Page(){
return (
<Layout>
<Header />
<Main>
<ProductsGrid />
</Main>
<Footer />
</Layout>
);
}Virtual DOM (VDOM) : pourquoi ça existe
Manipuler le DOM réel est coûteux (layout, paint, reflow). React crée une représentation en mémoire (arbre) et compare l’ancien et le nouveau pour ne mettre à jour que le nécessaire.
Réconciliation (reconciliation) : la mécanique
- State/props change → React relance le rendu du sous-arbre concerné.
- Nouvel arbre (description UI) en mémoire.
- Diffing : comparaison ancien/nouveau arbre.
- Commit : application minimale au DOM réel.
State change (count=1)
|
▼
Render (new tree)
|
▼
Diff old/new
|
▼
Commit (patch minimal DOM)
Ex:
Ancien: <p>0</p>
Nouveau: <p>1</p>
→ patch: textContent seulementPoint ultra important : les key en listes
Pour aider React à “matcher” les éléments d’une liste entre deux rendus, on donne une key stable. Sans key stable, React peut réutiliser des nœuds “au mauvais endroit” → bugs UI subtils (inputs, focus, animations).
{items.map(item => (
<Row key={item.id} item={item} /> // ✅ key stable
))}
// ❌ éviter key=index si la liste peut être réordonnée / filtréeLe pipeline de rendu : Render phase vs Commit phase
React sépare conceptuellement : Render (calcul de ce que l’UI devrait être) et Commit (application au DOM). C’est essentiel pour comprendre perf, effets, et comportements “concurrents” selon versions.
Render phase (calcul)
- Exécution des composants (fonctions) pour produire l’arbre UI.
- Peut être rejouée (en dev strict mode, ou selon scheduling).
- Doit rester “pure-ish” : pas d’effets de bord (API calls, mutations globales).
// Render doit être "safe":
function Comp(){
// ✅ calcul
const x = heavyCompute();
// ❌ éviter: side effects ici (fetch, localStorage write, etc.)
return <div>...</div>;
}Commit phase (application)
- React met à jour le DOM réel.
- Les effects (useEffect) s’exécutent après commit.
- Les “layout effects” (useLayoutEffect) s’exécutent avant paint.
React.useEffect(() => {
// ✅ side effects ici:
// fetch, subscriptions, analytics...
return () => {/* cleanup */}
}, [deps]);Conséquence pratique
- Si tu mets des effets de bord dans le rendu, tu crées des comportements imprévisibles.
- Les effets doivent être idempotents et nettoyables (cleanup) → robustesse.
Mental model “Senior” (ce qu’il faut vraiment retenir)
1) Single source of truth
- L’état doit vivre à un endroit clair (local, context, store).
- Éviter de dupliquer le même état dans 3 composants.
2) Data down, actions up
- Props descendent.
- Les événements remontent via callbacks (ou store).
3) Immutabilité (pragmatique)
- On ne mute pas “à la main” les structures d’état → on crée de nouvelles références.
- Pourquoi : comparaison par référence, memoization, predictibilité.
4) Performance = mesurer, pas deviner
- Optimiser trop tôt (useMemo/useCallback partout) = complexité inutile.
- Le vrai levier : architecture UI, découpage composants, keys, listes, virtualisation.
5) “Side effects” au bon endroit
- Rendu = calcul pur-ish.
- Effets = useEffect/useLayoutEffect, avec deps propres + cleanup.
Mini check-list “qualité”
| Question | Pourquoi | Signal d’alerte |
|---|---|---|
| Où vit le state ? | Lisibilité, partage, évite duplications | State copié dans plusieurs composants |
| Keys stables en listes ? | Réconciliation correcte | key=index sur liste triable/filtrable |
| Effects propres ? | Évite fuites, double calls, bugs | useEffect sans deps / sans cleanup |
| Perf mesurée ? | Optimisations pertinentes | useMemo/useCallback partout sans profiler |
JSX = “HTML dans JS”… mais pas vraiment
JSX (JavaScript XML) est une extension de syntaxe qui te permet d’écrire une UI lisible sous forme de balises dans du JavaScript. Ce n’est pas du HTML : c’est du sucre syntaxique transformé (transpilé) par un outil (ex: Babel, Vite, SWC) en appels de fonctions qui construisent une description d’UI.
Ce que JSX produit (idée)
// JSX
const el = <button className="btn">OK</button>;
// (concept) transpilation
// el = React.createElement("button", { className: "btn" }, "OK");Le point important : JSX décrit un arbre. React le “réconcilie” ensuite avec l’arbre précédent.
Pourquoi c’est utile
- Lisibilité : UI proche du résultat visuel.
- Composition : composants = balises réutilisables.
- Contraintes saines : règles explicites (className, htmlFor, etc.).
- Interop : tu peux mixer logique + UI sans “string templates”.
Règles de base (à connaître par cœur)
1) Un seul élément racine
// ✅ Fragment (pas de div inutile)
return (
<>
<Header />
<Main />
</>
);2) Toutes les balises doivent être fermées
// ✅ self-closing
<img src="/logo.png" alt="logo" />
<input value={q} onChange={...} />
// ❌ invalide en JSX
// <img src="/logo.png">3) Attributs “JS friendly”
class→classNamefor→htmlFor- Attributs en camelCase :
onClick,tabIndex,readOnly
4) Commentaires JSX
return (
<div>
{/* commentaire JSX */}
<p>Hello</p>
</div>
);5) Attributes booléens
<input disabled /> // ✅ true
<input disabled={false} /> // ✅ false (explicite)Expressions { } : ce qui est autorisé, ce qui ne l’est pas
Dans JSX, { } accepte une expression JavaScript (valeur), pas une suite d’instructions. Si tu as besoin de logique, calcule avant le return ou fais une fonction.
OK : expressions
const name = "Alice";
const isPro = true;
return (
<div>
<h3>Hello {name.toUpperCase()}</h3>
<p>Plan: {isPro ? "Pro" : "Free"}</p>
</div>
);Rendu conditionnel : patterns
// 1) AND (afficher si vrai)
{isOpen && <Modal />}
// 2) Ternaire (if/else UI)
{status === "ok" ? <Ok /> : <Error />}
// 3) Early return (souvent le plus clair)
if (loading) return <Spinner />;Attention : valeurs qui “s’affichent”
// ⚠️ 0 s'affiche (car 0 est une valeur rendable)
{items.length && <p>Items</p>}
// ✅ solution
{items.length > 0 && <p>Items</p>}Ce qui ne marche pas : statements
// ❌ pas de if directement dans JSX
return (
<div>
{ if(isPro){ <Pro /> } } // invalide
</div>
);
// ✅ faire avant
let content = isPro ? <Pro /> : <Free />;
return <div>{content}</div>;Listes, map() et la règle d’or : key stable
En React, les listes doivent toujours avoir une key stable (id métier), pas l’index, sinon tu risques des bugs subtils (inputs qui “échangent” leur valeur, focus perdu, animations qui glitch).
✅ Bonne liste
{users.map(u => (
<li key={u.id}>
<strong>{u.name}</strong>
</li>
))}keydoit être unique et stable (ne change pas si tri/filtre).- Évite
Math.random(): ça casse la réconciliation (tout re-mount).
❌ Problèmes courants
// ❌ key=index (si la liste peut bouger)
{items.map((it, i) => (
<Row key={i} item={it} />
))}Key=index peut être OK uniquement si la liste est statique (pas de tri, pas d’insert, pas de filtre).
Pattern : render “vide” proprement
{items.length === 0
? <EmptyState />
: <List items={items} />}Events, forms, refs : le trio du “vrai web”
Events : handlers propres
function Button(){
const handleClick = (e) => {
e.preventDefault();
// ...action
};
return (
<button onClick={handleClick}>Save</button>
);
}Forms : controlled input
function Login(){
const [email, setEmail] = React.useState("");
return (
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="email"
/>
);
}Ref : accès DOM (avec parcimonie)
function FocusMe(){
const ref = React.useRef(null);
React.useEffect(() => {
ref.current?.focus();
}, []);
return <input ref={ref} />;
}ref pour focus, mesure, intégration lib.ClassName dynamique (pattern)
const cls = `btn ${isActive ? "btn--active" : ""}`;
return <button className={cls}>OK</button>;En pratique, beaucoup utilisent une lib (ex: clsx) pour éviter les concaténations.
Pièges fréquents + règles “senior”
Pièges JSX
- Inline objects :
style=recrée un objet à chaque render (souvent OK, mais attention en perf). - Functions inline partout : pas grave par défaut, mais peut impacter memoization.
- DangerouslySetInnerHTML : risque XSS si contenu non sanitizé.
- Fragments : pense à
<>pour éviter DOM inutile.
Injection HTML (danger)
// ⚠️ XSS si html non contrôlé
<div dangerouslySetInnerHTML={{ __html: html }} />
Conventions de code (lisibilité)
- Limiter JSX “trop profond” : extraire en composants.
- Pré-calculer variables avant le
return(cls, flags, formatted text). - Éviter ternaires imbriqués → préférer “early return” ou fonctions.
- Nommer clairement les handlers :
handleSubmit,onSave…
Perf : règle simple
1) D'abord: rendre la UI claire et correcte
2) Ensuite: profiler (React DevTools Profiler)
3) Puis: optimiser le hotspot (memo, split, virtualization)TSX (TypeScript) : JSX + types = robustesse
Props typées
type UserCardProps = {
user: { id: string; name: string };
onSelect?: (id: string) => void;
};
function UserCard({ user, onSelect }: UserCardProps){
return (
<button onClick={() => onSelect?.(user.id)}>
{user.name}
</button>
);
}Children & ReactNode
type PanelProps = {
title: string;
children: React.ReactNode;
};
function Panel({ title, children }: PanelProps){
return (
<section>
<h3>{title}</h3>
<div>{children}</div>
</section>
);
}En TSX, tu sécurises l’API des composants (ce qui est un vrai “senior move” sur un gros projet).
Patterns TSX utiles
- Union types pour états:
"idle" | "loading" | "error" | "success" - Props discriminantes pour composants multi-variantes
- Types “readonly” sur données immuables
Pourquoi Vite est devenu le standard
Vite est un outil de build / dev server moderne conçu pour éliminer les lenteurs historiques des bundlers classiques. En dev, il s’appuie sur les ES Modules natifs du navigateur ; en prod, il bundle efficacement.
Avantages clés
- Démarrage instantané (pas de bundle complet au lancement).
- HMR ultra rapide (rechargement à chaud quasi immédiat).
- Config légère (fonctionne “out of the box”).
- Écosystème moderne (plugins, TS, JSX, CSS).
Positionnement
- Vite = tooling frontend (pas un framework).
- Parfait pour SPA React “classique”.
- Base idéale avant d’aller vers Next / Remix.
Installation React avec Vite (pas à pas)
# 1) Créer le projet
npm create vite@latest
# Questions interactives :
# ✔ Project name: my-react-app
# ✔ Framework: React
# ✔ Variant: JavaScript ou TypeScript
# 2) Aller dans le projet
cd my-react-app
# 3) Installer les dépendances
npm install
# 4) Lancer le serveur de dev
npm run devLe serveur démarre généralement sur http://localhost:5173.
Avec TypeScript (recommandé)
npm create vite@latest my-react-app -- --template react-ts
Structure d’un projet React + Vite
my-react-app/
├── public/ # Fichiers statiques (favicon, robots.txt)
├── src/
│ ├── assets/ # Images, fonts, styles globaux
│ ├── components/ # Composants UI réutilisables
│ ├── pages/ # Pages / vues (si SPA)
│ ├── hooks/ # Hooks custom
│ ├── services/ # API / data access
│ ├── App.jsx # Composant racine
│ ├── main.jsx # Point d’entrée React
│ └── index.css
├── index.html # Template HTML racine
├── package.json # Dépendances & scripts
├── vite.config.js # Configuration Vite
└── README.mdEn React, l’architecture du dossier = lisibilité du projet. Un senior structure très tôt (pages, services, hooks).
Scripts NPM & HMR
# package.json (extrait)
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}npm run dev
- Serveur de développement.
- HMR : modification instantanée sans reload page.
- Idéal pour itérations rapides.
npm run build / preview
- Build de production (assets optimisés).
preview= simuler la prod localement.
npm run build avant un merge important.vite.config.js (bases utiles)
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
open: true
},
build: {
sourcemap: true
}
});Cas courants
- Proxy API backend (dev) → éviter CORS.
- Alias de chemins (
@/components). - Variables d’environnement (
import.meta.env).
CRA, Vite, Next, Remix : qui fait quoi ?
| Outil | Statut | Usage |
|---|---|---|
| Vite | Standard moderne | SPA React rapide, flexible |
| Create React App | Legacy | À éviter pour nouveaux projets |
| Next.js | Framework | SSR, SSG, routing, fullstack |
| Remix | Framework | Data-driven, routing avancé |
Règle simple : Vite pour SPA, Next/Remix pour app web “complète”.
Setup “Senior” dès le jour 1
- Choisir TypeScript.
- Installer ESLint + Prettier.
- Structurer
components / pages / services / hooks. - Configurer alias de chemins.
- Prévoir scripts
lintettypecheck.
# Exemple outillage minimal
npm install -D eslint prettier
npm install axios
npm install react-router-domUn composant = une brique UI réutilisable
En React, un composant est une unité de rendu et de logique UI. Il reçoit des props (entrées), peut gérer du state (état local), et retourne du JSX (sortie UI). Les composants s’assemblent par composition.
Ce qu’un composant encapsule
- Structure (markup JSX)
- Style (className/CSS modules/Tailwind…)
- Comportement (handlers, state, effects)
- Contrat (API de props)
Deux grandes familles
- Function components (standard moderne) : hooks, plus simple, plus composable.
- Class components (legacy) : lifecycle methods, maintenance d’ancien code.
Mental model
Component(props) -> JSX
- props : entrée (paramètres)
- state : mémoire interne
- hooks : “outils” pour gérer state, effets, memo, refs
- composition : assembler une UI à partir de composantsFunction components (moderne) : la base
Un function component est une fonction JS/TS qui retourne du JSX. La logique “state & lifecycle” est gérée via hooks (useState, useEffect, etc.).
Composant simple (stateless)
function Badge({ label }) {
return <span className="badge">{label}</span>;
}Composant avec état
function Counter() {
const [count, setCount] = React.useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
</div>
);
}Pourquoi c’est mieux que les classes (en général)
- Moins de boilerplate (pas de
this, pas de constructors). - Composition de logique via hooks custom (réutilisation réelle).
- Lisibilité : props + state + rendu au même endroit.
- Interop : hooks modernes, libs modernes.
Règles importantes (Hooks)
- Appeler les hooks uniquement au top-level (pas dans des if/loops).
- Les hooks ne doivent être appelés que dans des components ou hooks custom.
- Le rendu doit rester “pur-ish” : pas d’effets de bord dans le
return.
Props, children & composition : le vrai pouvoir de React
Props = contrat
function Button({ variant = "primary", onClick, children }) {
const cls = `btn btn--${variant}`;
return (
<button className={cls} onClick={onClick}>
{children}
</button>
);
}children= contenu “slot” (composition).- Defaults = API ergonomique.
- Props doivent être stables et prévisibles.
Composition (Layout / Slots)
function Card({ title, actions, children }) {
return (
<section className="card">
<header>
<h3>{title}</h3>
<div>{actions}</div>
</header>
<div className="card__body">{children}</div>
</section>
);
}Pattern “slots” : tu passes des éléments React en props (actions) → très flexible sans complexité.
Data down, actions up
Parent -> props -> Child
Child -> callback/event -> Parent
Ex: <SearchBox value={q} onChange={setQ} />Class components (legacy) : quand et comment
Les classes étaient le standard avant les hooks. On les rencontre encore en maintenance. Elles gèrent l’état via this.state et le cycle de vie via des méthodes dédiées.
Exemple minimal
class CounterLegacy extends React.Component {
state = { count: 0 };
componentDidMount() {
// side effects (subscribe / fetch...)
}
componentWillUnmount() {
// cleanup
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
+1
</button>
</div>
);
}
}Lifecycle : mapping mental vers hooks
| Classe | Hooks (équivalent) | But |
|---|---|---|
| componentDidMount | useEffect(() => {}, []) | init / fetch / subscribe |
| componentDidUpdate | useEffect(() => {}, [deps]) | réagir à un changement |
| componentWillUnmount | return cleanup() dans useEffect | désinscription / cleanup |
this, bindings et aux effets de bord dispersés.Quand utiliser encore une classe ?
- Projet existant majoritairement en classes (cohérence).
- Refactor progressif (migration vers hooks par étapes).
- Cas rarissimes : error boundaries legacy (aujourd’hui mieux gérés via APIs modernes selon stack).
Patterns “Senior” (design de composants)
1) Séparer “container” et “presentational”
function UsersPage(){
const { data, isLoading } = useUsers(); // data + side effects
if(isLoading) return <Spinner />;
return <UsersList users={data} />; // UI pure-ish
}- Container : data, appels API, orchestration.
- Presentational : UI pure, props → JSX.
2) Custom hooks = réutilisation réelle
function useDebouncedValue(value, ms){
const [v, setV] = React.useState(value);
React.useEffect(() => {
const t = setTimeout(() => setV(value), ms);
return () => clearTimeout(t);
}, [value, ms]);
return v;
}3) API stable (ergonomie)
- Props simples + defaults.
- Éviter “god-components” avec 25 props.
- Nommer props comme une API produit :
onClose,isOpen,variant.
4) Inversion of control (slots)
<Card
title="Billing"
actions={<Button variant="ghost">Export</Button>}
>
<BillingTable />
</Card>Anti-patterns & performance : ce qui coûte cher en production
Anti-patterns
- God component : 800 lignes, tout mélangé (data + UI + effects).
- State dupliqué : même info stockée à plusieurs endroits.
- Props drilling extrême : 8 niveaux de props inutiles (solution : context/store, mais avec mesure).
- Effects mal cadrés : deps incorrectes → loops, sur-fetch, fuite mémoire.
Perf : leviers réels
- Découper composants pour limiter rerenders “en cascade”.
- Virtualiser les longues listes (1000+ rows).
- Éviter re-mount (keys stables).
- Profiler avant d’ajouter
memo/useMemo/useCallback.
Règle senior:
Profiler → identifier hotspot → optimiser
Pas l’inverse.Checklist “qualité composant” (review rapide)
| Point | Question | Signal d’alerte |
|---|---|---|
| API props | Est-ce clair, minimal, cohérent ? | 25 props / noms incohérents |
| State | Vit-il au bon niveau ? | Duplication / lifting non maîtrisé |
| Side effects | useEffect propre (deps + cleanup) ? | sur-fetch, loops, fuite mémoire |
| Composition | Peut-on réutiliser via children/slots ? | composant rigide, non composable |
| Perf | Problème prouvé au profiler ? | memo partout “au cas où” |
| Accessibilité | Semantic HTML, aria si besoin ? | div partout, pas de label |
Flux de données unidirectionnel (fondamental)
Les props sont le mécanisme standard pour transmettre des données d’un composant parent vers un composant enfant. Ce flux est unidirectionnel : on ne “remonte” jamais les données directement.
Parent → Enfant
function App(){
const user = { id: 1, name: "Alice", role: "admin" };
return (
<UserCard
name={user.name}
role={user.role}
isAdmin={user.role === "admin"}
/>
);
}Enfant (lecture seule)
function UserCard(props){
// ❌ interdit : mutation des props
// props.name = "Bob";
return (
<div>
<strong>{props.name}</strong>
<em>{props.role}</em>
</div>
);
}Règle d’or
Parent = source de vérité
Enfant = rendu + callbacksDestructuring : syntaxe propre et lisible
Plutôt que d’accéder à props.xxx partout, on déstructure les props directement dans la signature du composant.
Sans destructuring
function Badge(props){
return <span>{props.label}</span>;
}Avec destructuring (recommandé)
function Badge({ label }){
return <span>{label}</span>;
}Valeurs par défaut
function Button({ variant = "primary", disabled = false }){
return (
<button className={`btn-${variant}`} disabled={disabled}>
OK
</button>
);
}children : composition plutôt que configuration
children est une prop spéciale qui contient tout ce qui est passé entre les balises d’un composant. C’est la clé de la composition React.
Utilisation côté parent
<Card>
<h3>Titre</h3>
<p>Contenu libre</p>
</Card>Implémentation côté composant
function Card({ children }){
return (
<section className="card">
{children}
</section>
);
}Slots “avancés” via props
function Modal({ title, actions, children }){
return (
<div className="modal">
<header>
<h3>{title}</h3>
{actions}
</header>
<main>{children}</main>
</div>
);
}Pattern très “senior” : API flexible sans multiplier les props primitives.
Callbacks : faire remonter une action
Les props servent aussi à transmettre des fonctions. C’est ainsi qu’un enfant peut notifier le parent d’un événement.
Parent
function App(){
const [count, setCount] = React.useState(0);
return (
<Counter
value={count}
onIncrement={() => setCount(c => c + 1)}
/>
);
}Enfant
function Counter({ value, onIncrement }){
return (
<div>
<p>{value}</p>
<button onClick={onIncrement}>+1</button>
</div>
);
}Convention de nommage
onClick,onChange,onClose→ événementshandleX→ handlers internes
Props complexes : objets, tableaux, render props
Objets & tableaux
function UserList({ users }){
return (
<ul>
{users.map(u => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}Attention aux références : si users change à chaque render, le composant re-rendera (souvent OK, parfois à mesurer).
Render props (pattern avancé)
function DataFetcher({ render }){
const data = { value: 42 };
return render(data);
}
// Usage
<DataFetcher render={(d) => <span>{d.value}</span>} />Moins utilisé aujourd’hui (souvent remplacé par hooks), mais important à comprendre.
Typage des props : sécurité & documentation
TypeScript (recommandé)
type ButtonProps = {
variant?: "primary" | "secondary";
disabled?: boolean;
onClick: () => void;
};
function Button({ variant = "primary", disabled, onClick }: ButtonProps){
return (
<button disabled={disabled} onClick={onClick}>
OK
</button>
);
}PropTypes (legacy JS)
Button.propTypes = {
variant: PropTypes.string,
disabled: PropTypes.bool,
onClick: PropTypes.func.isRequired,
};PropTypes reste utile en JS pur, mais TS est devenu le standard.
Patterns senior & pièges courants
Bonnes pratiques
- API de props minimale et cohérente.
- Préférer
children/ slots à des flags multiples. - Documenter implicitement via types et noms clairs.
Props drilling
Passer une prop sur 6 niveaux est parfois un smell. Solutions possibles : lifting, composition, context (avec parcimonie).
Pièges
- Muter un objet reçu en prop (❌).
- Multiplier les booléens :
isSmall,isDark,isRounded… - Props trop génériques (
data,configfourre-tout).
Checklist rapide
| Question | Pourquoi | Red flag |
|---|---|---|
| Les props sont-elles immuables ? | Prévisibilité | Mutation directe |
| API claire ? | Lisibilité | Noms vagues |
| Callbacks bien nommés ? | Intention claire | Fonctions anonymes confuses |
| children utilisé à bon escient ? | Composition | Flags multiples inutiles |
Le State = la “mémoire” vivante du composant
Le state représente les données dynamiques d’un composant : celles qui peuvent changer suite à une interaction, un événement, ou un effet. Contrairement aux props (venues de l’extérieur), le state est local.
Exemples typiques de state
- Valeur d’un input / formulaire
- Compteur, toggle, onglet actif
- État de chargement (
loading) - Résultat d’un fetch (si local au composant)
Règles fondamentales
- Modifier le state ⇒ re-render du composant (et de ses enfants).
- Le state est persistant entre deux renders.
- Le state doit être minimal (pas de duplication inutile).
Mental model
Render = f(props, state)
state change
↓
re-render
↓
UI mise à jouruseState : API de base
useState est le hook qui permet d’ajouter un état local à un composant fonctionnel. Il retourne toujours un couple : la valeur courante et une fonction de mise à jour.
Exemple simple
import { useState } from "react";
function Counter(){
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}Ce qui se passe
- Premier render →
count = 0 - Clic →
setCount(1) - React déclenche un nouveau render
- UI reflète la nouvelle valeur
Valeur initiale “lourde”
// Lazy init (calcul exécuté une seule fois)
const [data, setData] = useState(() => expensiveInit());Pattern utile pour éviter des calculs coûteux à chaque render.
Immutabilité : règle NON négociable
React détecte les changements de state par référence. Muter directement un objet ou un tableau empêche React de voir le changement.
❌ Mauvais (mutation directe)
const [user, setUser] = useState({ name: "Alice", age: 30 });
const birthday = () => {
user.age += 1; // mutation
setUser(user); // même référence
};React peut ne pas re-render → bug silencieux.
✅ Bon (copie immuable)
const [user, setUser] = useState({ name: "Alice", age: 30 });
const birthday = () => {
setUser({
...user,
age: user.age + 1
});
};Arrays : patterns sûrs
// Ajouter
setItems(prev => [...prev, newItem]);
// Supprimer
setItems(prev => prev.filter(i => i.id !== id));
// Mettre à jour
setItems(prev =>
prev.map(i => i.id === id ? { ...i, done: true } : i)
);Mises à jour, batching & fonctions de mise à jour
Les mises à jour de state peuvent être batchées. Il est donc dangereux de se baser sur une valeur “courante” lorsqu’on fait plusieurs updates successives.
❌ Problème potentiel
setCount(count + 1);
setCount(count + 1); // peut rester à +1✅ Fonction de mise à jour
setCount(prev => prev + 1);
setCount(prev => prev + 1); // +2 garantiOù placer le state ? (question clé d’architecture)
State local
- Utilisé par un seul composant
- Ex: toggle, input, dropdown
State levé (lifting state up)
- Partagé par plusieurs enfants
- Vit dans le parent commun
// Lifting state up
function Parent(){
const [value, setValue] = useState("");
return (
<>
<Input value={value} onChange={setValue} />
<Preview value={value} />
</>
);
}Le bon placement du state simplifie tout le reste (props, perf, tests).
Patterns “Senior” autour du state
1) State minimal
- Ne stocker que la donnée source.
- Dériver le reste au render.
// ❌ inutile
const [fullName, setFullName] = useState("Alice Doe");
// ✅ mieux
const fullName = `${firstName} ${lastName}`;2) Séparer logique & UI
function useCounter(){
const [count, setCount] = useState(0);
const inc = () => setCount(c => c + 1);
return { count, inc };
}Les hooks custom encapsulent la logique de state.
3) Quand ne PAS utiliser useState
- Donnée dérivable des props
- Donnée globale (préférer context/store)
- Donnée temporaire sans impact UI
Pièges courants & checklist
Pièges
- Muter le state directement
- Dupliquer la même donnée dans plusieurs states
- setState en boucle infinie (via effects)
- État trop profond (objets imbriqués complexes)
Checklist rapide
- Le state est-il minimal ?
- Immuable à chaque update ?
- Bon niveau dans l’arbre ?
- Fonctionnelle si dépendance à l’ancien state ?
Phrase à retenir
“Si ton state est bien placé et immuable,
ton composant devient simple et prévisible.”useEffect = gérer ce qui n’est PAS du rendu
Un composant React doit rester pur pendant le rendu (calcul JSX). Tout ce qui interagit avec “l’extérieur” (API, timers, DOM, subscriptions) est un effet de bord et doit vivre dans useEffect.
Exemples d’effets
- Appels API / data fetching
- WebSocket, EventSource, abonnements
setInterval,setTimeoutdocument.title, scroll, focus
Signature générale
useEffect(() => {
// 1) effet (après render)
return () => {
// 2) cleanup (optionnel)
};
}, [
// 3) dépendances
]);Mental model
Render (JSX)
↓
Commit DOM
↓
useEffect s’exécute[] — effet au montage (équivalent componentDidMount)
Avec un tableau de dépendances vide, l’effet s’exécute une seule fois, juste après le premier rendu.
useEffect(() => {
console.log("Mounted");
fetch("/api/init").then(...);
}, []);Cas d’usage typiques
- Chargement initial de données
- Initialisation d’une lib externe
- Abonnement global (avec cleanup)
Attention (React Strict Mode)
En mode développement, React peut exécuter l’effet deux fois pour détecter les effets non idempotents.
[deps] — effet à la mise à jour
Si tu fournis des dépendances, l’effet s’exécute : au montage puis à chaque changement d’une dépendance.
useEffect(() => {
console.log("userId changed:", userId);
fetch(`/api/users/${userId}`).then(...);
}, [userId]);Que mettre dans les deps ?
- Toutes les variables utilisées dans l’effet
- Props et state référencés
- Fonctions non stables (sauf memo)
Erreur fréquente
// ❌ dépendance manquante
useEffect(() => {
doSomething(value);
}, []); // value utilisé mais absentBug subtil : effet non synchronisé avec le state réel.
Cleanup — équivalent componentWillUnmount
Si ton effet crée un abonnement ou un processus long, tu dois nettoyer lors du démontage (ou avant ré-exécution).
useEffect(() => {
const id = setInterval(() => {
console.log("tick");
}, 1000);
return () => {
clearInterval(id);
};
}, []);À nettoyer absolument
- Timers (
setInterval,setTimeout) - WebSocket / EventSource
- Event listeners (
addEventListener)
Règle senior
Si tu t’abonnes → tu te désabonnes.
Data fetching avec useEffect
Le pattern classique : état loading, data, error. Attention aux race conditions.
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch("/api/data")
.then(r => r.json())
.then(d => {
if (!cancelled) setData(d);
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, []);En pratique, beaucoup utilisent des libs dédiées (query cache), mais comprendre ce pattern est essentiel.
Dépendances & ESLint (exhaustive-deps)
La règle ESLint exhaustive-deps t’indique quelles dépendances manquent dans le tableau.
Bon réflexe
- Corriger l’effet plutôt que désactiver la règle
- Extraire la logique dans une fonction stable
- Utiliser
useCallbacksi nécessaire
❌ Mauvaise pratique
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
doSomething(value);
}, []);Acceptable uniquement si tu maîtrises parfaitement l’impact.
Pièges courants & checklist senior
Pièges
- Mettre trop de logique dans un seul effect
- Oublier le cleanup
- Dépendances incomplètes
- Effet qui modifie le state déclenchant lui-même
Checklist rapide
- Est-ce vraiment un effet ?
- Dépendances complètes et correctes ?
- Cleanup présent si abonnement ?
- Effet idempotent ?
useEffect mal maîtrisé.Phrase à retenir
“Si ton rendu n’est pas pur,
ton useEffect est au mauvais endroit.”Gestion des événements en React
Les événements React (onClick, onChange, onSubmit, etc.) utilisent une syntaxe camelCase et reçoivent une fonction. On ne passe jamais une string ou un appel direct.
Handler classique
function AlertButton(){
const handleClick = (e) => {
e.preventDefault();
alert("Clic !");
};
return (
<button onClick={handleClick}>
Ne pas cliquer
</button>
);
}Inline handler (si simple)
<button onClick={() => alert("Simple")}>
Clic simple
</button>OK pour actions triviales. Sinon, préférer une fonction nommée.
Événements courants
onClick— interaction utilisateuronChange— saisie input/selectonSubmit— envoi formulaireonBlur / onFocus— focusonKeyDown— clavier
SyntheticEvent : abstraction cross-browser
React encapsule les événements natifs dans un SyntheticEvent, garantissant un comportement cohérent entre navigateurs.
function Example(e){
console.log(e.type); // click
console.log(e.target); // élément DOM
console.log(e.currentTarget);
}e.persist()).Accès aux valeurs
onChange={(e) => {
const value = e.target.value;
}}Controlled Components (principe fondamental)
En React, un formulaire est dit contrôlé lorsque la valeur affichée dans l’input est pilotée par le state. Le state devient la source de vérité.
Input contrôlé
function NameInput(){
const [name, setName] = React.useState("");
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Votre nom"
/>
);
}Pourquoi c’est puissant
- Validation instantanée
- Formatage automatique
- Synchronisation UI / data
Formulaire complet (submit, validation)
function LoginForm(){
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const handleSubmit = (e) => {
e.preventDefault();
console.log({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Login</button>
</form>
);
}Plusieurs champs (pattern)
const [form, setForm] = useState({ email: "", pwd: "" });
onChange={(e) =>
setForm(f => ({ ...f, [e.target.name]: e.target.value }))
}Patterns “Senior”
1) Un state par formulaire
- Évite la multiplication de
useState - Facilite reset / validation
2) Validation dérivée
const isValid = email.includes("@") && password.length > 8;3) Soumission async
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
await api.login(form);
setLoading(false);
};Anti-patterns & pièges
❌ Non contrôlé sans raison
<input defaultValue="test" />
OK uniquement pour formulaires très simples / legacy.
❌ Handler lourd inline
<button onClick={() => doA(); doB(); doC();}>Préférer une fonction dédiée.
Autres pièges
- Oublier
preventDefault()sur submit - Mettre trop de logique dans JSX
- Pas de label → accessibilité cassée
Checklist UX & qualité
| Point | Question | Red flag |
|---|---|---|
| Contrôle | Le state est-il la source de vérité ? | Inputs désynchronisés |
| UX | Feedback loading / erreur ? | Formulaire silencieux |
| Accessibilité | Label + id associés ? | Inputs non labellisés |
| Lisibilité | Handlers nommés clairement ? | Fonctions inline complexes |
Phrase à retenir
“En React, un formulaire propre est
contrôlé, prévisible et accessible.”Rendu conditionnel : 4 patterns propres
En JSX, on ne met pas un if/else “brut” directement dans le markup. On utilise des expressions : early return, ternaire, &&, ou on prépare une variable.
1) Early return (souvent le plus clair)
function Page(){
if (loading) return <Spinner />;
if (error) return <ErrorBox />;
return <Dashboard />;
}2) Ternaire (if/else inline)
<h1>{user ? `Bonjour, ${user.name}` : "Bonjour, visiteur"}</h1>3) AND (if… alors)
{isAdmin && <AdminBadge />}⚠️ Attention au “0”
// ❌ 0 s'affiche si items.length = 0
{items.length && <p>Items</p>}
// ✅ mieux
{items.length > 0 && <p>Items</p>}4) Préparer une variable
let content = <Empty />;
if (items.length > 0) content = <List items={items} />;
return <div>{content}</div>;Listes : .map() = transformer des données en UI
On rend une liste en transformant un tableau de données en tableau d’éléments JSX. Le JSX accepte directement un tableau d’éléments.
Exemple simple
function TodoList({ todos }){
return (
<ul>
{todos.map(t => (
<li key={t.id}>{t.text}</li>
))}
</ul>
);
}Return implicite (style concis)
{todos.map(t => (
<TodoRow key={t.id} todo={t} />
))}Filtrer + mapper (pattern courant)
{todos
.filter(t => t.done)
.map(t => (
<li key={t.id}>{t.text}</li>
))}Rendu “empty” propre
{todos.length === 0
? <EmptyState title="Aucune tâche" />
: <TodoList todos={todos} />}key : crucial pour le diffing (VDOM / réconciliation)
React doit identifier chaque item pour savoir s’il a été ajouté, supprimé ou déplacé. La prop key est l’identifiant unique et stable pour chaque élément.
✅ Bonne key
<li key={todo.id}>...</li>- Stable : ne change pas si la liste est triée/filtrée
- Unique : pas de duplication
- Idéal : ID base de données / UUID / slug
❌ Anti-pattern : key=index
{todos.map((t, i) => (
<TodoRow key={i} todo={t} />
))}Ne pas utiliser l’index si la liste peut bouger (tri, suppression au milieu, insertion).
Symptômes : inputs qui changent de valeur, focus qui saute, animations incohérentes, state mélangé entre lignes.
Key et “remount” volontaire
Parfois, tu veux forcer le remount d’un composant (reset state interne) en changeant sa key.
<UserForm key={userId} userId={userId} /> // change userId => reset du formEmpty / Loading / Error : le triptyque UX
Une UI “pro” gère toujours les 3 états : chargement, erreur, vide. Sinon l’app semble “cassée” ou silencieuse.
function UsersPanel(){
const { data, loading, error } = useUsers();
if (loading) return <SkeletonRows />;
if (error) return <ErrorBox message="Impossible de charger" />;
if (!data || data.length === 0) return <EmptyState title="Aucun utilisateur" />;
return <UsersTable users={data} />;
}Loading UX
- Skeletons > spinner (perception de vitesse)
- Conserver layout pour éviter “layout shift”
Error UX
- Message clair + action (Retry)
- Logs / monitoring (en prod)
Patterns senior : rendre le JSX lisible
1) Render helper (fonction locale)
function Panel({ items, loading }){
const renderBody = () => {
if (loading) return <Spinner />;
if (items.length === 0) return <Empty />;
return <List items={items} />;
};
return <section>{renderBody()}</section>;
}2) Extraire en composants
return (
<section>
<Header />
<ListOrEmpty items={items} />
</section>
);Si ton composant mélange trop de branches de rendu, extraire = meilleur refactor “ROI”.
3) Branching UI par “state machine” (simple)
const status = loading ? "loading" : error ? "error" : items.length ? "ok" : "empty";
return (
<>
{status === "loading" && <Skeleton />}
{status === "error" && <ErrorBox />}
{status === "empty" && <Empty />}
{status === "ok" && <List items={items} />}
</>
);Pièges réels (et comment les éviter)
1) Ternaires imbriqués
// ❌ illisible
{a ? (b ? <X/> : <Y/>) : (c ? <Z/> : <W/>)}✅ Solution : early return / variable / composants.
2) Keys instables
// ❌ remount à chaque render
<Row key={Math.random()} />3) map sans return (piège JS)
// ❌ { } nécessite un return explicite
{items.map(i => {
<li key={i.id}>{i.name}</li>
})}
// ✅
{items.map(i => (
<li key={i.id}>{i.name}</li>
))}4) Rendu de “falsy” inattendu
// "" rend rien (OK) mais 0 s'affiche
{value && <Tag />}Checklist qualité (code review)
| Point | Question | Red flag |
|---|---|---|
| Conditions | Le JSX reste lisible ? | Ternaires imbriqués |
| Listes | La liste a-t-elle une key stable ? | key=index / random |
| UX | Empty/loading/error gérés ? | Écran vide “sans explication” |
| Perf | Listes longues optimisées ? | 1000+ nodes DOM sans virtualisation |
| Bug class | Focus stable / inputs OK ? | State mélangé entre items |
Phrase à retenir
“Rendu lisible + keys stables + états UX gérés
= UI React robuste.”Data fetching “vanilla” avec useEffect
Le pattern de base combine : useState (data / loading / error) + useEffect (déclenchement). Simple, mais vite répétitif et fragile sur des apps réelles.
function Articles(){
const [data, setData] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
React.useEffect(() => {
let cancelled = false;
const fetchData = async () => {
try {
const res = await fetch("/api/articles");
if (!res.ok) throw new Error("API error");
const json = await res.json();
if (!cancelled) setData(json);
} catch (e) {
if (!cancelled) setError(e.message);
} finally {
if (!cancelled) setLoading(false);
}
};
fetchData();
return () => { cancelled = true; };
}, []);
if (loading) return <Skeleton />;
if (error) return <ErrorBox message={error} />;
return <ArticleList items={data} />;
}Axios : client HTTP plus ergonomique
Axios simplifie la gestion des headers, du JSON, des erreurs et permet des interceptors (auth, refresh token).
Client centralisé
// api/client.ts
import axios from "axios";
export const api = axios.create({
baseURL: "/api",
timeout: 5000,
});
api.interceptors.response.use(
res => res,
err => Promise.reject(err.response?.data || err)
);Utilisation
const { data } = await api.get("/articles");
setArticles(data);Très utile pour auth JWT, headers globaux, retry custom.
Problèmes classiques du data fetching “manuel”
- Duplication de code (
loading/errorpartout) - Race conditions (réponses hors ordre)
- Pas de cache → refetch inutile
- Gestion complexe des refresh / refocus
- Synchronisation inter-composants difficile
// Exemple de bug fréquent
useEffect(() => {
fetch(`/api/users/${id}`).then(setUser);
}, [id]); // id change vite → réponses mélangéesReact Query (TanStack Query) — le standard moderne
React Query gère le serveur-state : cache, refetch, loading, error, synchronisation automatique.
Query simple
import { useQuery } from "@tanstack/react-query";
const fetchArticles = async () => {
const res = await fetch("/api/articles");
if (!res.ok) throw new Error("API error");
return res.json();
};
function Articles(){
const { data, isLoading, error } = useQuery({
queryKey: ["articles"],
queryFn: fetchArticles,
});
if (isLoading) return <Skeleton />;
if (error) return <ErrorBox />;
return <ArticleList items={data} />;
}Ce que React Query apporte
- Cache automatique
- Refetch au focus / reconnect
- Déduplication des requêtes
- Pagination & infinite queries
Mutations (POST / PUT / DELETE)
Les mutations modifient le serveur-state. React Query gère loading, error et invalidation du cache.
import { useMutation, useQueryClient } from "@tanstack/react-query";
function AddArticle(){
const qc = useQueryClient();
const mutation = useMutation({
mutationFn: (data) =>
fetch("/api/articles", {
method: "POST",
body: JSON.stringify(data),
}),
onSuccess: () => {
qc.invalidateQueries(["articles"]);
},
});
return (
<button onClick={() => mutation.mutate({ title: "New" })}>
Add
</button>
);
}Invalidation = synchronisation automatique de toute l’app.
Architecture “Senior” recommandée
Organisation
services/api.ts(clients HTTP)queries/*.ts(hooks React Query)- Composants = UI uniquement
Exemple hook dédié
// queries/useArticles.ts
export const useArticles = () =>
useQuery({
queryKey: ["articles"],
queryFn: fetchArticles,
});Checklist & choix technologiques
| Besoin | Solution |
|---|---|
| Prototype simple | useEffect + fetch |
| Auth / headers complexes | Axios |
| App pro / scalable | React Query |
| Cache & sync multi-composants | React Query |
Phrase à retenir
“Le state serveur n’est pas du state local :
traite-le comme une ressource partagée.”React n’inclut pas de routeur
Pour une SPA, la librairie standard est React Router (DOM). Elle mappe une URL vers un composant “page” sans recharger la page.
npm install react-router-dom
main.jsx — setup minimal
On “enveloppe” l’app avec <BrowserRouter>.
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);Routes v6 : Routes, Route, Layout & Outlet
Le pattern “pro” : un composant Layout (Navbar + footer) qui encadre les pages via <Outlet />.
import { Routes, Route } from "react-router-dom";
import Layout from "./components/Layout";
import HomePage from "./pages/HomePage";
import AboutPage from "./pages/AboutPage";
import UserProfile from "./pages/UserProfile";
import NotFound from "./pages/NotFound";
export default function App(){
return (
<Routes>
{/* Layout partagé */}
<Route path="/" element={<Layout />}>
{/* index = page "/" */}
<Route index element={<HomePage />} />
{/* pages simples */}
<Route path="about" element={<AboutPage />} />
{/* route dynamique */}
<Route path="users/:userId" element={<UserProfile />} />
{/* 404 */}
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
);
}Layout.jsx — Outlet
import { Outlet } from "react-router-dom";
import Navbar from "./Navbar";
export default function Layout(){
return (
<div className="app-shell">
<Navbar />
<main className="container">
<Outlet />
</main>
<footer>© IDEO-Lab</footer>
</div>
);
}Navigation interne : Link et NavLink
Pour naviguer dans une SPA, on utilise <Link> (pas <a href>), afin d’éviter le rechargement complet.
Link
import { Link } from "react-router-dom";
<nav>
<Link to="/">Accueil</Link>
<Link to="/about">À propos</Link>
</nav>NavLink (active state)
import { NavLink } from "react-router-dom";
<NavLink
to="/about"
className={({ isActive }) => isActive ? "nav-item active" : "nav-item"}
>
À propos
</NavLink>Pratique pour mettre en surbrillance l’onglet actif dans un menu.
Relative links (dans routes imbriquées)
// depuis /users
<Link to="42">User 42</Link> // -> /users/42Routes dynamiques : :param + useParams
Une route dynamique capture des segments d’URL pour construire une page “détail”. Exemple: /users/42 ⇒ userId = 42.
import { useParams } from "react-router-dom";
export default function UserProfile(){
const { userId } = useParams();
// ex: useEffect(() => fetch(`/api/users/${userId}`), [userId])
return <h1>Profil de l'utilisateur {userId}</h1>;
}useParams() renvoie des strings : si tu attends un nombre, parse/valide côté code.Navigation “code” : useNavigate
Quand tu dois naviguer après une action (login, submit, save), utilise useNavigate().
import { useNavigate } from "react-router-dom";
function Login(){
const navigate = useNavigate();
const onSuccess = () => {
navigate("/dashboard"); // go to
// navigate(-1); // back
// navigate("/users/42", { replace: true });
};
return <button onClick={onSuccess}>Login</button>;
}Lire / écrire les query params
import { useSearchParams } from "react-router-dom";
function Products(){
const [params, setParams] = useSearchParams();
const q = params.get("q") || "";
return (
<input
value={q}
onChange={(e) => setParams({ q: e.target.value })}
/>
);
}404, redirections & guards (routes protégées)
404 (catch-all)
<Route path="*" element={<NotFound />} />Redirection
import { Navigate } from "react-router-dom";
<Route path="old" element={<Navigate to="/new" replace />} />Route protégée (auth guard)
import { Navigate, Outlet } from "react-router-dom";
function RequireAuth({ isAuthed }){
if (!isAuthed) return <Navigate to="/login" replace />;
return <Outlet />;
}
// Usage
<Route element={<RequireAuth isAuthed={isAuthed} />}>
<Route path="dashboard" element={<Dashboard />} />
</Route><Outlet /> si OK, sinon redirige.Important (prod) : config serveur
En BrowserRouter, un refresh sur /about doit renvoyer index.html. Sinon : 404 côté serveur. Il faut donc une règle “fallback to index.html”.
Patterns senior & pièges
Bonnes pratiques
- Routes par feature (pages regroupées par domaine).
- Layout + routes imbriquées (Outlet) = structure claire.
- Utiliser
NavLinkpour l’état “actif”. - Gérer 404 / loading / error proprement.
Code splitting (lazy)
import { lazy, Suspense } from "react";
const Settings = lazy(() => import("./pages/Settings"));
<Route
path="settings"
element={
<Suspense fallback={<Spinner />}>
<Settings />
</Suspense>
}
/>Pièges fréquents
- <a href> au lieu de <Link> → reload complet.
- Oublier le fallback serveur → 404 au refresh.
- Routes trop plates (pas de Layout) → duplication de wrappers.
- Paramètres non validés (IDs) → erreurs silencieuses.
Checklist express
| Point | OK ? | Note |
|---|---|---|
| Layout + Outlet | ✅ | structure stable |
| 404 catch-all | ✅ | UX propre |
| NavLink actif | ✅ | navigation claire |
| Fallback serveur | ✅ | prod stable |
Prop Drilling : le symptôme classique
Le prop drilling consiste à faire transiter une donnée à travers plusieurs composants intermédiaires qui n’en ont pas besoin, uniquement pour atteindre un composant lointain.
App (user)
└─ Layout (passe user)
└─ Sidebar (passe user)
└─ Page
└─ UserProfile (utilise user)- Composants pollués par des props inutiles
- Couplage fort entre couches
- Refactors douloureux
Context : un “tunnel” de données
Context permet de rendre une valeur disponible partout dans un sous-arbre, sans la passer explicitement en props.
createContext → Provider → useContext
Quand utiliser Context ?
- Utilisateur authentifié
- Thème (dark/light)
- Langue / i18n
- Permissions / rôles
Quand éviter ?
- Données très locales
- Données très volatiles (perf)
- State complexe avec logique lourde
Mise en place pas à pas
1️⃣ Créer le contexte
import { createContext } from "react";
export const AuthContext = createContext(null);2️⃣ Fournir la valeur (Provider)
import { AuthContext } from "./AuthContext";
function App(){
const user = { name: "Alice", role: "admin" };
return (
<AuthContext.Provider value={user}>
<Layout />
</AuthContext.Provider>
);
}3️⃣ Consommer la valeur
import { useContext } from "react";
import { AuthContext } from "./AuthContext";
function UserProfile(){
const user = useContext(AuthContext);
return <h1>Bonjour {user.name}</h1>;
}user.Context + State (cas réel)
Très souvent, le contexte expose un state + des actions.
export const AuthContext = createContext(null);
function AuthProvider({ children }){
const [user, setUser] = useState(null);
const login = (u) => setUser(u);
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
// Usage
const { user, logout } = useContext(AuthContext);Patterns senior
1) Custom hook
export const useAuth = () => {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used in AuthProvider");
return ctx;
};Simplifie l’import et sécurise l’usage.
2) Context découpés
AuthContextThemeContextSettingsContext
Évite un “God Context” gigantesque.
3) Context + useReducer
const [state, dispatch] = useReducer(reducer, initialState);
Recommandé quand la logique devient plus complexe.
Limites & alternatives
| Besoin | Solution |
|---|---|
| Partager 1–2 valeurs simples | useContext |
| State serveur (API) | React Query |
| State global complexe | Redux / Zustand / Jotai |
| Perf très critique | Store spécialisé |
Checklist & pièges
Pièges
- Mettre tout le state global dans un seul Context
- Utiliser Context pour chaque petit state
- Oublier le Provider →
null
Checklist
- Évite-t-il vraiment le prop drilling ?
- Valeur stable / memo si besoin ?
- Context limité à un domaine clair ?
Phrase à retenir
“Context est un excellent outil…
à condition de ne pas en faire un mini-Redux.”Pourquoi dépasser useContext ?
useContext est parfait pour des données peu volatiles (thème, utilisateur connecté). Dès que le state devient fréquent, volumineux ou critique, il faut un store global optimisé.
Problèmes typiques
- Re-renders globaux inutiles
- Logique métier dispersée
- Difficulté de debug
- Synchronisation complexe
Quand un store global est justifié
- Dashboard complexe
- Workflow multi-pages
- État partagé par de nombreux composants
- Besoin de time-travel / debug
Redux Toolkit (RTK) — standard entreprise
Redux Toolkit est la version moderne et recommandée de Redux. Il réduit drastiquement le boilerplate historique tout en gardant une architecture prédictible et robuste.
UI → dispatch(action)
↓
reducer (fonction pure)
↓
store (state global)
↓
useSelector → UIConcepts clés
- Store : source unique de vérité
- Slice : domaine fonctionnel (user, cart, settings)
- Reducer : logique de mise à jour
- Action : intention (“user/login”)
Redux Toolkit — exemple réaliste
Slice
import { createSlice } from "@reduxjs/toolkit";
const userSlice = createSlice({
name: "user",
initialState: { user: null },
reducers: {
login: (state, action) => {
state.user = action.payload;
},
logout: (state) => {
state.user = null;
},
},
});
export const { login, logout } = userSlice.actions;
export default userSlice.reducer;Store
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";
export const store = configureStore({
reducer: {
user: userReducer,
},
});Utilisation dans un composant
import { useSelector, useDispatch } from "react-redux";
import { login } from "./userSlice";
function Login(){
const dispatch = useDispatch();
const user = useSelector(s => s.user.user);
return (
<button onClick={() => dispatch(login({ name: "Alice" }))}>
Login
</button>
);
}Avantages : traçabilité, testabilité, DevTools puissants.
Zustand — minimalisme et efficacité
Zustand est un store global ultra-léger basé sur des hooks. Pas de Provider, pas de reducers, très peu de code.
import { create } from "zustand";
export const useStore = create((set) => ({
count: 0,
inc: () => set(s => ({ count: s.count + 1 })),
reset: () => set({ count: 0 }),
}));Consommation
function Counter(){
const count = useStore(s => s.count);
const inc = useStore(s => s.inc);
return <button onClick={inc}>{count}</button>;
}Redux Toolkit vs Zustand
| Critère | Redux Toolkit | Zustand |
|---|---|---|
| Boilerplate | Moyen | Très faible |
| Scalabilité | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| Debug / DevTools | Excellent | Basique |
| Courbe d’apprentissage | Plus raide | Très douce |
| Cas idéal | App entreprise | App moderne / rapide |
Patterns senior
Redux
- Un slice par domaine métier
- Sélecteurs mémorisés
- Async via RTK Query
Zustand
- Store par feature
- State + actions ensemble
- Pas de “God store”
Pièges & checklist
Pièges
- Tout mettre dans le store global
- Dupliquer le server state
- Reducers trop génériques
Checklist
- Le state est-il vraiment global ?
- Server state séparé ?
- Sélecteurs précis ?
Phrase à retenir
“Un bon store global rend l’app prévisible,
pas compliquée.”React = build-time : dev server ≠ production
Le serveur de dev (npm run dev) sert à itérer vite (HMR). En production, on déploie un bundle statique généré par npm run build (Vite) dans dist/.
# Build production
npm run build
# Résultat (Vite)
dist/
index.html
assets/
index-xxxx.js
index-yyyy.cssCe que fait le build
- Minification JS/CSS + tree-shaking
- Bundling + hashing des assets (cache busting)
- Optimisation des imports, chunks, sourcemaps (option)
Ce que tu déploies
- Uniquement
dist/(ou le contenu servi) - Pas
node_modules - Pas le dev server
Preview local “comme en prod”
Toujours tester le bundle prod localement avant de pousser en prod. Avec Vite : npm run preview sert le dossier dist/.
npm run build
npm run preview
# ouvre http://localhost:4173 (souvent)Checklist QA
- Routage (refresh sur
/route) - 404 (page not found) & redirections
- Console propre (pas d’erreurs)
- Env vars (API URL) correctes
Conseil “pro”
- Build + lint + tests en CI
- Build “reproductible” (lockfile)
- Preview deploy (PR previews sur Vercel/Netlify)
Variables d’environnement (Vite) : import.meta.env
En Vite, les variables exposées au frontend doivent être préfixées par VITE_. Elles sont injectées au build (important).
# .env.production
VITE_API_BASE_URL=https://api.monsite.com// usage
const API = import.meta.env.VITE_API_BASE_URL;.env côté client.Base path (si app servie sous un sous-répertoire)
// vite.config.js
export default defineConfig({
base: "/react/", // exemple si app servie sur https://site.com/react/
});Déploiement statique (Jamstack) : Vercel / Netlify
Le plus simple pour une SPA : pousser sur GitHub/GitLab, connecter Vercel/Netlify, et laisser le CI/CD faire.
Configuration typique
- Build command :
npm run build - Output directory :
dist - Node version : fixée (ex: 18/20) via settings
- Env vars : dans le dashboard (prod/staging)
SPA routing (très important)
React Router gère les routes côté client. Il faut une règle “fallback to index.html”.
# Netlify: _redirects (dans /public ou dist selon setup)
/* /index.html 200# Vercel (concept)
toute route -> index.html (SPA)Bonus : preview deploy par pull request
- Chaque PR ⇒ URL de preview (QA rapide)
- Merge main ⇒ prod auto
Déploiement Nginx (VPS / bare-metal)
Tu buildes en CI (ou local), tu copies dist/ sur le serveur, et Nginx sert les fichiers. Le point critique : fallback SPA (sinon 404 au refresh).
# Copier dist/ sur le serveur
rsync -avz dist/ user@server:/var/www/react-app/Nginx config (SPA + cache + fallback)
# /etc/nginx/sites-available/react-app
server {
listen 80;
server_name example.com;
root /var/www/react-app;
index index.html;
# assets hashés : cache long
location /assets/ {
try_files $uri =404;
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
}HTTPS (pro)
- Certbot / Let’s Encrypt (ou reverse proxy existant)
- Redirect HTTP → HTTPS
Docker (pro) : build multi-stage + Nginx
Pattern standard : builder Node ⇒ artefacts statiques ⇒ image Nginx légère.
# Dockerfile (multi-stage)
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
# SPA fallback custom (option)
# COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80Run
docker build -t react-app .
docker run -p 8080:80 react-appPourquoi Docker ici ?
- Build reproductible
- Déploiement simple (K8s, Swarm, ECS)
- Rollback facile
Perf, cache, sécurité : ce qui fait la différence en prod
Perf & cache
- Assets hashés : cache long (immutable)
- HTML : cache court (ou no-cache)
- Compression : gzip/brotli (selon infra)
- Code splitting (lazy routes)
- Images optimisées + lazy loading
Sourcemaps
- Utile pour debugging (Sentry, etc.)
- À contrôler selon politique sécurité
Sécurité (headers)
- CSP (Content-Security-Policy) si possible
- HSTS en HTTPS
- X-Content-Type-Options nosniff
- Referrer-Policy
Checklist “release”
| Item | OK ? | Notes |
|---|---|---|
npm run build clean | ✅ | pas d’erreurs/warnings critiques |
npm run preview validé | ✅ | routing + env |
| Fallback SPA en prod | ✅ | Nginx / Netlify / Vercel |
| Cache assets hashés | ✅ | immutable |
| Monitoring erreurs | ✅ | Sentry / logs |
Phrase à retenir
“Build statique + routing SPA + cache correct
= déploiement React robuste.”🧠 Hooks fondamentaux
1️⃣ useState — état local
const [count, setCount] = useState(0);
// update simple
setCount(1);
// update basé sur l'ancien state (recommandé)
setCount(c => c + 1);
// array / object (immutabilité)
setItems(prev => [...prev, newItem]);
setUser(prev => ({ ...prev, age: prev.age + 1 }));2️⃣ useEffect — effets & lifecycle
// mount (1 fois)
useEffect(() => {
fetchData();
}, []);
// dépendance
useEffect(() => {
fetch(`/api/users/${id}`);
}, [id]);
// cleanup
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);3️⃣ useContext — state partagé
const ThemeContext = createContext("light");
function App(){
return (
<ThemeContext.Provider value="dark">
<Page />
</ThemeContext.Provider>
);
}
const theme = useContext(ThemeContext);useTheme())⚙️ Hooks avancés & perf
4️⃣ useRef — DOM & valeur persistante
// DOM
const inputRef = useRef(null);
<input ref={inputRef} />
inputRef.current.focus();
// valeur persistante (sans re-render)
const renderCount = useRef(0);
renderCount.current++;5️⃣ useMemo — mémoïsation valeur
const total = useMemo(() => {
return items.reduce((a, b) => a + b.price, 0);
}, [items]);6️⃣ useCallback — mémoïsation fonction
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);useMemo/useCallback seulement si utile📋 Récap express (quand utiliser quoi)
| Hook | Rôle | À utiliser quand… | À éviter si… |
|---|---|---|---|
useState | État local | UI interactive | Donnée dérivable |
useEffect | Effets de bord | API, timers, subscriptions | Logique de rendu |
useContext | State partagé | Auth, thème, settings | State très fréquent |
useRef | Référence mutable | DOM, valeur persistante | UI réactive |
useMemo | Perf (valeur) | Calcul coûteux | Calcul trivial |
useCallback | Perf (fonction) | Props enfants memo | Handlers simples |
⌨️ CLI rapide (Vite + React)
# créer projet
npm create vite@latest my-app
cd my-app
# installer deps
npm install
# dev
npm run dev
# build prod
npm run build
# preview prod
npm run preview
# deps courantes
npm install react-router-dom
npm install @tanstack/react-query
npm install zustand🧩 Phrase à retenir
“Si tu maîtrises useState, useEffect et useContext,
tu comprends déjà 80 % de React.”