# Fichier: python_cheats/cheatsheets/react.txt
# Cheatsheet React.js - Guide Complet et Technique


[OK] INTRODUCTION - QU'EST-CE QUE REACT ?


React est une bibliothèque JavaScript créée par Facebook en 2013 pour construire
des interfaces utilisateur (UI) modernes, interactives et performantes.

# === ANALOGIE SIMPLE POUR COMPRENDRE ===

Imaginez que vous construisez une maison avec des LEGO :

[ACCUEIL] APPLICATION WEB = Maison complète
[BRICK] COMPOSANTS REACT = Briques LEGO individuelles
[PACKAGE] JSX = Instructions pour assembler les briques
[DESIGN] PROPS = Couleurs/formes des briques
[SYNC] STATE = Briques qui peuvent changer (portes qui s'ouvrent, lumières qui s'allument)

Comme avec des LEGO, vous créez des petites pièces (composants) que vous
assemblez pour créer quelque chose de plus grand (votre application).

# === Concepts fondamentaux ===

1. COMPOSANTS:
   - Briques de base de React
   - Fonction ou classe JavaScript qui retourne du JSX (HTML-like)
   - Réutilisables et composables
   - Exemple: Un bouton, un formulaire, une carte utilisateur

2. JSX (JavaScript XML):
   - Syntaxe qui ressemble à HTML mais dans du JavaScript
   - Permet d'écrire l'UI de manière déclarative
   - Compilé en JavaScript pur par Babel
   
   // JSX
   const element = <h1>Hello World</h1>;
   
   // Devient (JavaScript pur)
   const element = React.createElement('h1', null, 'Hello World');

3. ÉTAT (STATE):
   - Données dynamiques d'un composant
   - Quand l'état change, React re-render automatiquement
   - Comparable aux variables d'instance dans une classe Python

4. PROPS (Properties):
   - Paramètres passés à un composant (comme arguments de fonction)
   - Lecture seule (immutables)
   - Permettent la communication parent -> enfant

5. RENDU DÉCLARATIF:
   - Vous décrivez CE QUE vous voulez afficher, pas COMMENT
   - React s'occupe de mettre à jour le DOM efficacement
   
   # Impératif (JavaScript classique)
   const div = document.createElement('div');
   div.textContent = 'Hello';
   document.body.appendChild(div);
   
   # Déclaratif (React)
   return <div>Hello</div>;

6. VIRTUAL DOM:
   - React maintient une copie du DOM en mémoire
   - Compare avant/après chaque changement
   - Met à jour seulement ce qui a changé (performance optimale)

7. UNIDIRECTIONAL DATA FLOW:
   - Les données circulent dans un seul sens: parent -> enfant
   - Rend l'application prévisible et facile à debugger


# === Comparaison avec autres approches ===

FLASK (Backend):
- Génère HTML côté serveur
- Envoie une page complète à chaque requête
- Rechargement complet de la page

REACT (Frontend):
- HTML généré côté client (navigateur)
- Seules les données JSON sont échangées avec le backend
- Mise à jour partielle de l'interface (pas de rechargement)

Architecture moderne:
┌─────────────┐      JSON/REST API       ┌─────────────┐
│   REACT     │ <-──────────────────────-> │   FLASK     │
│  (Frontend) │                          │  (Backend)  │
│  Port 3000  │                          │  Port 5000  │
└─────────────┘                          └─────────────┘
 Navigateur                               Serveur


# === POURQUOI REACT A ÉTÉ CRÉÉ ? ===

PROBLÈME 1: Sites web traditionnels lents
  Avant React : Chaque changement rechargeait toute la page
  Avec React : Seule la partie modifiée se met à jour
  
  Exemple concret :
  - Facebook a 1000 notifications -> sans React, il faut recharger toute la page
  - Avec React, seul le compteur de notifications se met à jour

PROBLÈME 2: Code difficile à maintenir
  Avant React : HTML, CSS et JavaScript mélangés partout
  Avec React : Tout est organisé en petits morceaux réutilisables
  
  Exemple :
  - Bouton "J'aime" utilisé 50 fois sur Facebook
  - Sans React : Copier-coller 50 fois le code (cauchemar !)
  - Avec React : Créer 1 composant <LikeButton />, l'utiliser 50 fois

PROBLÈME 3: Difficile de gérer les interactions
  Avant React : JavaScript complexe pour synchroniser l'interface
  Avec React : Vous dites CE QUE vous voulez, React s'occupe du COMMENT


# === CONCEPT 1 : COMPOSANTS (LES BRIQUES DE BASE) ===

Un composant = Une fonction JavaScript qui retourne du HTML

ANALOGIE : Un composant est comme une recette de cuisine
- Vous donnez des ingrédients (props)
- La recette les transforme
- Vous obtenez un plat (l'interface affichée)


## Exemple ultra-simple : Composant de bienvenue

```javascript
// Composant le plus simple possible
function Welcome() {
  return <h1>Bonjour !</h1>;
}

// Pour l'afficher dans votre page
<Welcome />
```

EXPLICATION LIGNE PAR LIGNE :

1. `function Welcome()` 
   -> On crée une fonction JavaScript normale
   -> Le nom DOIT commencer par une MAJUSCULE (convention React)

2. `return <h1>Bonjour !</h1>;`
   -> La fonction retourne du "HTML" (en fait du JSX, on verra ça)
   -> C'est ce qui sera affiché à l'écran

3. `<Welcome />`
   -> Pour utiliser notre composant, on l'écrit comme une balise HTML
   -> C'est comme appeler la fonction Welcome()


## Exemple avec paramètres (props)

```javascript
// Composant qui accepte un nom en paramètre
function Welcome(props) {
  return <h1>Bonjour, {props.name} !</h1>;
}

// Utilisation
<Welcome name="Alice" />  -> Affiche : "Bonjour, Alice !"
<Welcome name="Bob" />    -> Affiche : "Bonjour, Bob !"
```

EXPLICATION :

1. `function Welcome(props)`
   -> props = objet JavaScript contenant tous les paramètres
   -> Comme les arguments d'une fonction Python : def welcome(name):

2. `{props.name}`
   -> Les accolades {} permettent d'insérer du JavaScript dans le HTML
   -> props.name récupère la valeur du paramètre "name"

3. `<Welcome name="Alice" />`
   -> On passe le paramètre "name" avec la valeur "Alice"
   -> C'est comme appeler : welcome(name="Alice") en Python


## Comparaison avec Python (si vous connaissez Python)

```python
# PYTHON - Fonction qui génère du HTML
def welcome(name):
    return f"<h1>Bonjour, {name} !</h1>"

# Appel
print(welcome("Alice"))  # <h1>Bonjour, Alice !</h1>
```

```javascript
// REACT - Composant qui fait la même chose
function Welcome({ name }) {
  return <h1>Bonjour, {name} !</h1>;
}

// Utilisation
<Welcome name="Alice" />  {/* Affiche : Bonjour, Alice ! */}
```

DIFFÉRENCES CLÉS :
[OK] Python : Retourne une STRING (texte) contenant du HTML
[OK] React : Retourne du JSX qui devient automatiquement du HTML dans le navigateur


# === CONCEPT 2 : JSX (HTML DANS JAVASCRIPT) ===

JSX = JavaScript XML = HTML qui ressemble à du JavaScript


## Qu'est-ce que le JSX exactement ?

IMPORTANT : Le JSX n'est PAS du HTML !
C'est du JavaScript déguisé en HTML pour être plus lisible.


### Exemple : Comment le JSX est transformé

```javascript
// CE QUE VOUS ÉCRIVEZ (JSX)
const element = <h1>Bonjour !</h1>;

// CE QUE BABEL (compilateur) GÉNÈRE (JavaScript pur)
const element = React.createElement('h1', null, 'Bonjour !');
```

EXPLICATION :
- Vous écrivez du JSX qui ressemble à du HTML (facile à lire)
- Babel le transforme en JavaScript pur (ce que le navigateur comprend)
- React.createElement crée un objet JavaScript représentant l'élément


### Pourquoi utiliser JSX ?

```javascript
// SANS JSX (JavaScript pur) - DIFFICILE À LIRE
const element = React.createElement(
  'div',
  { className: 'container' },
  React.createElement('h1', null, 'Titre'),
  React.createElement('p', null, 'Paragraphe')
);

// AVEC JSX - FACILE À LIRE
const element = (
  <div className="container">
    <h1>Titre</h1>
    <p>Paragraphe</p>
  </div>
);
```

C'est comme comparer :
- Code machine (0101010) <- Dur à lire
- Code Python (print("Hello")) <- Facile à lire


## Règles importantes du JSX

### RÈGLE 1 : Expressions JavaScript entre accolades {}

```javascript
const name = 'Alice';
const age = 25;

// [OK] CORRECT : Utiliser {} pour insérer du JavaScript
<h1>Bonjour, {name} !</h1>
<p>Tu as {age} ans</p>
<p>Dans 5 ans, tu auras {age + 5} ans</p>

// [X] INCORRECT : Sans accolades, c'est du texte littéral
<h1>Bonjour, name !</h1>  -> Affiche : "Bonjour, name !"
```

ANALOGIE : Les accolades {} sont comme les f-strings en Python
```python
# Python f-string
name = "Alice"
print(f"Bonjour, {name} !")  # Bonjour, Alice !

# React JSX
<h1>Bonjour, {name} !</h1>  {/* Bonjour, Alice ! */}
```


### RÈGLE 2 : Expressions seulement, pas de statements

```javascript
// [OK] EXPRESSIONS (retournent une valeur)
{2 + 2}                           -> 4
{user.name}                       -> "Alice"
{isLoggedIn ? 'Bienvenue' : 'Connexion'}  -> Ternaire OK
{items.map(item => <li>{item}</li>)}      -> Map OK

// [X] STATEMENTS (ne retournent pas de valeur)
{if (x > 5) { ... }}              -> Interdit !
{for (let i = 0; i < 10; i++) {}} -> Interdit !
{const x = 5;}                    -> Interdit !
```

POURQUOI ? Le JSX attend une VALEUR à afficher, pas du code d'exécution.

SOLUTIONS :
```javascript
// Au lieu de if/else, utilisez ternaire
{age >= 18 ? <Adult /> : <Child />}

// Au lieu de for loop, utilisez map
{items.map(item => <Item key={item.id} data={item} />)}

// Si logique complexe, mettez-la AVANT le return
function Component() {
  // Logique ici
  let message;
  if (age >= 18) {
    message = 'Adulte';
  } else {
    message = 'Enfant';
  }
  
  // JSX simple
  return <h1>{message}</h1>;
}
```


### RÈGLE 3 : className au lieu de class

```javascript
// [X] HTML traditionnel
<div class="container">Content</div>

// [OK] JSX
<div className="container">Content</div>
```

POURQUOI ?
- "class" est un mot-clé réservé en JavaScript (class MyClass {})
- React utilise "className" pour éviter les conflits


### RÈGLE 4 : Fermer toutes les balises

```javascript
// [X] HTML (peut marcher sans fermeture)
<input type="text">
<img src="photo.jpg">

// [OK] JSX (DOIT être fermé)
<input type="text" />
<img src="photo.jpg" />
```


### RÈGLE 5 : Un seul élément racine

```javascript
// [X] INCORRECT : Deux éléments racines
return (
  <h1>Titre</h1>
  <p>Paragraphe</p>
);

// [OK] SOLUTION 1 : Wrapper div
return (
  <div>
    <h1>Titre</h1>
    <p>Paragraphe</p>
  </div>
);

// [OK] SOLUTION 2 : Fragment (pas de div dans le DOM)
return (
  <>
    <h1>Titre</h1>
    <p>Paragraphe</p>
  </>
);
```

POURQUOI ? Une fonction JavaScript ne peut retourner qu'UNE SEULE valeur.


## Exemples pratiques de JSX

### Exemple 1 : Afficher une liste

```javascript
function FruitList() {
  const fruits = ['Pomme', 'Banane', 'Orange'];
  
  return (
    <ul>
      {fruits.map((fruit, index) => (
        <li key={index}>{fruit}</li>
      ))}
    </ul>
  );
}

// Résultat affiché :
// • Pomme
// • Banane
// • Orange
```

EXPLICATION :
1. `fruits.map()` -> Parcourt chaque fruit du tableau
2. Pour chaque fruit, crée un `<li>` avec le nom du fruit
3. `key={index}` -> Identifiant unique (React en a besoin pour optimiser)


### Exemple 2 : Affichage conditionnel

```javascript
function Greeting({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? (
        <h1>Bienvenue de retour !</h1>
      ) : (
        <h1>Veuillez vous connecter</h1>
      )}
    </div>
  );
}

// Utilisation
<Greeting isLoggedIn={true} />   -> "Bienvenue de retour !"
<Greeting isLoggedIn={false} />  -> "Veuillez vous connecter"
```


### Exemple 3 : Styles inline

```javascript
function StyledBox() {
  // Style = objet JavaScript (pas string CSS !)
  const style = {
    backgroundColor: 'blue',   // backgroundColor (camelCase)
    color: 'white',
    padding: '20px',
    fontSize: '18px'           // fontSize (camelCase)
  };
  
  return <div style={style}>Je suis stylé !</div>;
}
```

DIFFÉRENCES avec CSS :
- `background-color` -> `backgroundColor` (camelCase)
- `font-size` -> `fontSize`
- Valeurs en string : `'20px'` au lieu de `20px`


# === CONCEPT 3 : ÉTAT (STATE) - LES DONNÉES QUI CHANGENT ===

L'état (state) = Données qui peuvent changer au fil du temps
Quand l'état change -> React re-affiche automatiquement l'interface


## Analogie : Compteur de likes sur Facebook

SANS REACT (HTML/JavaScript classique) :
```html
<p>Likes : <span id="count">0</span></p>
<button onclick="incrementCount()">J'aime</button>

<script>
let count = 0;
function incrementCount() {
  count++;
  // PROBLÈME : Il faut MANUELLEMENT mettre à jour l'affichage
  document.getElementById('count').textContent = count;
}
</script>
```

AVEC REACT :
```javascript
function LikeButton() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Likes : {count}</p>
      <button onClick={() => setCount(count + 1)}>
        J'aime
      </button>
    </div>
  );
}
```

MAGIE DE REACT :
1. Vous changez count avec setCount(count + 1)
2. React détecte le changement AUTOMATIQUEMENT
3. React re-affiche SEULEMENT ce qui a changé
4. Pas besoin de toucher au DOM manuellement !


## useState : Le hook pour créer un état

```javascript
import { useState } from 'react';

function Counter() {
  //          v valeur    v fonction pour changer   v valeur initiale
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Compteur : {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}
```

DÉCOMPOSITION :
1. `useState(0)` -> Crée un état avec valeur initiale 0
2. Retourne un tableau de 2 éléments :
   - [0] = count -> La valeur actuelle
   - [1] = setCount -> La fonction pour modifier count

3. `const [count, setCount] = ...`
   -> Destructuration de tableau (syntaxe JavaScript)
   -> Équivalent à :
   ```javascript
   const state = useState(0);
   const count = state[0];
   const setCount = state[1];
   ```


## Comment fonctionne la mise à jour d'état ?

```javascript
function Counter() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    // [OK] CORRECT : Utiliser setCount pour modifier
    setCount(count + 1);
    
    // [X] INCORRECT : Modifier directement count
    // count = count + 1;  <- Ne fonctionne PAS !
  };
  
  return (
    <div>
      <p>Compteur : {count}</p>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}
```

RÈGLE D'OR : TOUJOURS utiliser la fonction setter (setCount)
Jamais modifier directement la variable (count)

POURQUOI ?
- Si vous faites `count = 5`, React ne le sait PAS
- Si vous faites `setCount(5)`, React sait qu'il faut re-afficher
- C'est comme ça que React peut être performant


## Flux de données complet

```javascript
function Counter() {
  const [count, setCount] = useState(0);
  
  console.log('1. Composant rendu avec count =', count);
  
  const handleClick = () => {
    console.log('2. Clic détecté, count avant =', count);
    setCount(count + 1);
    console.log('3. setCount appelé');
    // count vaut toujours 0 ici ! (mise à jour asynchrone)
  };
  
  return (
    <div>
      <p>Compteur : {count}</p>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}

// DÉROULEMENT :
// 1. Premier rendu : count = 0
// 2. Utilisateur clique
// 3. handleClick s'exécute, setCount(1) est appelé
// 4. React PLANIFIE un re-rendu
// 5. Deuxième rendu : count = 1
// 6. L'interface se met à jour
```


## État avec différents types de données

```javascript
function Examples() {
  // Nombre
  const [age, setAge] = useState(25);
  
  // String
  const [name, setName] = useState('Alice');
  
  // Boolean
  const [isActive, setIsActive] = useState(true);
  
  // Tableau
  const [items, setItems] = useState(['Pomme', 'Banane']);
  
  // Objet
  const [user, setUser] = useState({
    name: 'Alice',
    age: 25,
    email: 'alice@example.com'
  });
  
  return <div>...</div>;
}
```


# === CONCEPT 4 : PROPS (PROPRIÉTÉS) - PASSER DES DONNÉES ===

Props = Paramètres passés à un composant (comme arguments de fonction)


## Analogie : Props = Ingrédients de recette

```javascript
// Recette de gâteau (composant)
function Cake({ flavor, layers }) {
  return (
    <div>
      <h2>Gâteau au {flavor}</h2>
      <p>{layers} étages</p>
    </div>
  );
}

// Faire différents gâteaux (utiliser le composant)
<Cake flavor="chocolat" layers={3} />  -> Gâteau au chocolat, 3 étages
<Cake flavor="vanille" layers={2} />   -> Gâteau à la vanille, 2 étages
```

EXPLICATION :
- `Cake` = Fonction (composant)
- `flavor` et `layers` = Paramètres (props)
- Vous pouvez créer autant de gâteaux différents que vous voulez !


## Props : Lecture seule (immutables)

```javascript
function Greeting({ name }) {
  // [X] INTERDIT : Modifier les props
  name = 'Bob';  // Erreur ! Props sont en lecture seule
  
  // [OK] CORRECT : Utiliser les props telles quelles
  return <h1>Bonjour, {name} !</h1>;
}
```

RÈGLE : Props = ENTRÉES du composant, vous ne pouvez PAS les modifier


## Comparaison Props vs State

```
┌─────────────────────────────────────────────────────────────┐
│  PROPS                    │  STATE                          │
├───────────────────────────┼─────────────────────────────────┤
│  Passées par le parent    │  Gérées par le composant        │
│  Lecture seule            │  Modifiables (avec setter)      │
│  Changent quand parent    │  Changent avec setState         │
│  change                   │                                 │
│  Comme arguments fonction │  Comme variables locales        │
└─────────────────────────────────────────────────────────────┘
```


## Exemple complet Props + State

```javascript
// Composant ENFANT : Reçoit des props, ne peut pas les modifier
function Button({ text, onClick }) {
  return <button onClick={onClick}>{text}</button>;
}

// Composant PARENT : A un état, passe des props à l'enfant
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Compteur : {count}</p>
      <Button 
        text="Incrémenter" 
        onClick={() => setCount(count + 1)} 
      />
    </div>
  );
}
```

FLUX DE DONNÉES :
```
Counter (parent)
  │
  │ État: count = 0
  │
  ├─ Passe props: text="Incrémenter"
  │                onClick={fonction}
  │
  └─> Button (enfant)
       │
       │ Reçoit props (lecture seule)
       │
       └─ Quand cliqué, appelle props.onClick
          │
          └─> Remonte au parent qui modifie son état
```


# === CONCEPT 5 : VIRTUAL DOM - POURQUOI REACT EST RAPIDE ===

## Le DOM traditionnel (lent)

DOM = Document Object Model = Représentation de la page HTML en JavaScript

PROBLÈME avec manipulation directe du DOM :
```javascript
// Chaque ligne = Recalcul complet du navigateur (LENT)
document.getElementById('title').textContent = 'Nouveau titre';
document.getElementById('count').textContent = '42';
document.getElementById('user').textContent = 'Alice';
// = 3 recalculs de toute la page !!!
```


## Le Virtual DOM de React (rapide)

Virtual DOM = Copie légère du DOM en mémoire (JavaScript pur)

COMMENT ÇA MARCHE :

```
┌──────────────────────────────────────────────────────────┐
│  1. État change (ex: count passe de 5 à 6)              │
│                                                          │
│  2. React crée un NOUVEAU Virtual DOM avec count = 6    │
│                                                          │
│  3. React COMPARE ancien vs nouveau Virtual DOM          │
│     (algorithme ultra-rapide)                           │
│                                                          │
│  4. React trouve les DIFFÉRENCES :                       │
│     -> Seulement le nombre "6" a changé                  │
│                                                          │
│  5. React met à jour UNIQUEMENT ce qui a changé         │
│     dans le vrai DOM                                    │
└──────────────────────────────────────────────────────────┘
```

RÉSULTAT : Au lieu de refaire toute la page, React ne touche QUE
            le petit bout qui a changé = ULTRA RAPIDE !


## Exemple concret

```javascript
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Faire courses' },
    { id: 2, text: 'Appeler maman' },
    { id: 3, text: 'Coder en React' }
  ]);
  
  const addTodo = () => {
    setTodos([...todos, { id: 4, text: 'Nouvelle tâche' }]);
  };
  
  return (
    <div>
      {todos.map(todo => (
        <div key={todo.id}>{todo.text}</div>
      ))}
      <button onClick={addTodo}>Ajouter</button>
    </div>
  );
}
```

QUAND VOUS CLIQUEZ :
1. setTodos ajoute un nouveau todo
2. React crée nouveau Virtual DOM avec 4 todos
3. React compare : "Ah, 1 todo de plus à la fin"
4. React ajoute SEULEMENT le nouveau <div> dans le vrai DOM
5. Les 3 premiers todos ne sont PAS retouchés (performance !)


# === CONCEPT 6 : FLUX DE DONNÉES UNIDIRECTIONNEL ===

Données vont TOUJOURS du parent vers l'enfant (jamais l'inverse direct)


## Schéma du flux

```
        Grand-Parent
             │
        (props v)
             │
          Parent
         │       │
    (props v) (props v)
         │       │
      Enfant1  Enfant2
```

RÈGLES :
[OK] Parent peut passer données à Enfant (via props)
[X] Enfant NE PEUT PAS modifier props du Parent directement
[OK] Enfant peut DEMANDER au Parent de changer (via callback)


## Exemple : Enfant communique avec Parent

```javascript
// PARENT : A l'état et passe fonction à l'enfant
function Parent() {
  const [message, setMessage] = useState('Bonjour');
  
  const handleChange = (newMessage) => {
    setMessage(newMessage);
  };
  
  return (
    <div>
      <p>Message : {message}</p>
      <Child onMessageChange={handleChange} />
    </div>
  );
}

// ENFANT : Reçoit fonction en prop et l'appelle
function Child({ onMessageChange }) {
  return (
    <button onClick={() => onMessageChange('Salut !')}>
      Changer message
    </button>
  );
}
```

FLUX :
```
1. Parent a message = "Bonjour"
2. Parent passe onMessageChange à Child
3. Child clique sur bouton
4. Child appelle onMessageChange('Salut !')
5. Parent exécute handleChange('Salut !')
6. Parent met à jour message = "Salut !"
7. React re-rend Parent et Child avec nouveau message
```


# === CONCEPT 7 : COMPOSANTS = RÉUTILISABLES ===

Le vrai pouvoir de React : Créer une fois, utiliser partout


## Exemple : Bouton réutilisable

```javascript
// Définir le composant UNE FOIS
function Button({ text, color, onClick }) {
  const style = {
    backgroundColor: color,
    color: 'white',
    padding: '10px 20px',
    border: 'none',
    borderRadius: '5px',
    cursor: 'pointer'
  };
  
  return (
    <button style={style} onClick={onClick}>
      {text}
    </button>
  );
}

// Utiliser PARTOUT avec différentes props
function App() {
  return (
    <div>
      <Button text="Valider" color="green" onClick={() => alert('Validé')} />
      <Button text="Annuler" color="red" onClick={() => alert('Annulé')} />
      <Button text="Modifier" color="blue" onClick={() => alert('Modifié')} />
    </div>
  );
}
```

AVANTAGES :
[OK] Code écrit une seule fois
[OK] Si bug dans Button, on le corrige UNE fois et ça marche partout
[OK] Design cohérent dans toute l'app
[OK] Facile à tester


# === COMPARAISON REACT VS APPROCHES TRADITIONNELLES ===

## Approche 1 : HTML/CSS/JS séparés (old school)

```html
<!-- index.html -->
<div id="app">
  <h1 id="title">Bienvenue</h1>
  <p id="count">0</p>
  <button id="btn">Cliquer</button>
</div>

<script src="script.js"></script>
```

```javascript
// script.js
let count = 0;

document.getElementById('btn').addEventListener('click', () => {
  count++;
  document.getElementById('count').textContent = count;
});
```

PROBLÈMES :
[X] HTML et JavaScript séparés (difficile de voir le lien)
[X] Il faut manuellement mettre à jour le DOM
[X] Code difficile à réutiliser
[X] Pas de composants


## Approche 2 : Avec React

```javascript
// App.jsx (tout au même endroit)
function App() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <h1>Bienvenue</h1>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>
        Cliquer
      </button>
    </div>
  );
}
```

AVANTAGES :
[OK] HTML et logique ensemble (facile à comprendre)
[OK] React met à jour automatiquement l'affichage
[OK] Composant réutilisable
[OK] Code plus court et plus clair


# === RÉSUMÉ DES CONCEPTS FONDAMENTAUX ===

```
┌────────────────────────────────────────────────────────────┐
│  CONCEPT          │  C'EST QUOI ?          │  ANALOGIE     │
├───────────────────┼────────────────────────┼───────────────┤
│  COMPOSANTS       │  Briques réutilisables │  Pièces LEGO  │
│                   │  qui retournent du JSX │               │
├───────────────────┼────────────────────────┼───────────────┤
│  JSX              │  HTML dans JavaScript  │  f-string     │
│                   │  (mais c'est du JS !)  │  Python       │
├───────────────────┼────────────────────────┼───────────────┤
│  STATE            │  Données qui changent  │  Variables    │
│                   │  au fil du temps       │  mutables     │
├───────────────────┼────────────────────────┼───────────────┤
│  PROPS            │  Paramètres passés à   │  Arguments    │
│                   │  un composant          │  fonction     │
├───────────────────┼────────────────────────┼───────────────┤
│  VIRTUAL DOM      │  Copie du DOM en       │  Brouillon    │
│                   │  mémoire pour optimiser│  avant copie  │
├───────────────────┼────────────────────────┼───────────────┤
│  FLUX             │  Données vont parent -> │  Cascade      │
│  UNIDIRECTIONNEL  │  enfant seulement      │  d'eau        │
└────────────────────────────────────────────────────────────┘
```


# === PREMIER PAS : CRÉER VOTRE PREMIÈRE APP ===

## Prérequis : Installer Node.js

Node.js = Environnement pour exécuter JavaScript en dehors du navigateur
npm = Gestionnaire de paquets (comme pip pour Python)

1. Télécharger Node.js : https://nodejs.org/
2. Installer (version LTS recommandée)
3. Vérifier installation :
   ```bash
   node --version    # v18.0.0 ou plus
   npm --version     # v9.0.0 ou plus
   ```


## Méthode 1 : Create React App (CRA) - Pour débutants

Create React App = Outil officiel qui configure tout automatiquement

```bash
# Créer une nouvelle application
npx create-react-app mon-app

# Entrer dans le dossier
cd mon-app

# Démarrer le serveur de développement
npm start
```

EXPLICATION :
- `npx` = Exécute un package sans l'installer globalement
- `create-react-app` = Nom du package
- `mon-app` = Nom de votre projet
- `npm start` = Lance l'app sur http://localhost:3000


## Structure du projet créé

```
mon-app/
├── node_modules/      # Dépendances (comme venv/ en Python)
├── public/            # Fichiers statiques
│   ├── index.html     # Page HTML de base
│   └── favicon.ico    # Icône du site
├── src/               # Votre code source
│   ├── App.js         # Composant principal
│   ├── App.css        # Styles de App
│   ├── index.js       # Point d'entrée (comme main.py)
│   └── index.css      # Styles globaux
├── package.json       # Dépendances (comme requirements.txt)
└── README.md          # Documentation
```


## Fichier index.html (public/index.html)

```html
<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="utf-8" />
    <title>Mon App React</title>
  </head>
  <body>
    <!-- React va injecter l'app ICI v -->
    <div id="root"></div>
  </body>
</html>
```

IMPORTANT : React prend le contrôle du `<div id="root">` et y affiche toute votre app


## Fichier index.js (src/index.js) - Point d'entrée

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

// Trouver l'élément #root dans le HTML
const root = ReactDOM.createRoot(document.getElementById('root'));

// Afficher le composant App dans #root
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```

EXPLICATION LIGNE PAR LIGNE :
1. `import React from 'react'` -> Importer React
2. `import ReactDOM from 'react-dom/client'` -> Importer pour affichage
3. `import App from './App'` -> Importer votre composant principal
4. `ReactDOM.createRoot(...)` -> Créer point d'entrée React
5. `root.render(<App />)` -> Afficher App dans le DOM


## Fichier App.js (src/App.js) - Votre premier composant

```javascript
import './App.css';

function App() {
  return (
    <div className="App">
      <h1>Bienvenue dans React !</h1>
      <p>Ceci est votre première application.</p>
    </div>
  );
}

export default App;
```

MODIFIEZ CE FICHIER et sauvegardez -> La page se recharge automatiquement !
(Hot Reloading = rechargement automatique)


# === EXERCICE PRATIQUE : COMPTEUR SIMPLE ===

## Objectif : Créer un compteur interactif

Remplacez le contenu de src/App.js par ceci :

```javascript
import { useState } from 'react';
import './App.css';

function App() {
  // Créer un état "count" avec valeur initiale 0
  const [count, setCount] = useState(0);
  
  // Fonction pour incrémenter
  const increment = () => {
    setCount(count + 1);
  };
  
  // Fonction pour décrémenter
  const decrement = () => {
    setCount(count - 1);
  };
  
  // Fonction pour réinitialiser
  const reset = () => {
    setCount(0);
  };
  
  return (
    <div className="App">
      <h1>Compteur React</h1>
      
      {/* Afficher la valeur actuelle */}
      <p className="count-display">
        Valeur : {count}
      </p>
      
      {/* Boutons pour modifier le compteur */}
      <div className="buttons">
        <button onClick={decrement}>- 1</button>
        <button onClick={reset}>Reset</button>
        <button onClick={increment}>+ 1</button>
      </div>
      
      {/* Message conditionnel */}
      {count > 10 && (
        <p className="message">Wow, vous avez dépassé 10 !</p>
      )}
      
      {count < 0 && (
        <p className="message">Le compteur est négatif !</p>
      )}
    </div>
  );
}

export default App;
```

Ajoutez ces styles dans src/App.css :

```css
.App {
  text-align: center;
  padding: 50px;
  font-family: Arial, sans-serif;
}

.count-display {
  font-size: 48px;
  font-weight: bold;
  color: #333;
  margin: 30px 0;
}

.buttons {
  display: flex;
  gap: 10px;
  justify-content: center;
  margin: 20px 0;
}

button {
  padding: 15px 30px;
  font-size: 18px;
  cursor: pointer;
  border: none;
  border-radius: 5px;
  background-color: #007bff;
  color: white;
  transition: background-color 0.3s;
}

button:hover {
  background-color: #0056b3;
}

.message {
  font-size: 20px;
  color: #ff6b6b;
  font-weight: bold;
  margin-top: 20px;
}
```


## Analyse du code

### 1. Import de useState
```javascript
import { useState } from 'react';
```
On importe le hook useState pour gérer l'état


### 2. Création de l'état
```javascript
const [count, setCount] = useState(0);
```
- `count` = Valeur actuelle du compteur
- `setCount` = Fonction pour modifier count
- `0` = Valeur de départ


### 3. Fonctions handlers
```javascript
const increment = () => {
  setCount(count + 1);
};
```
Fonction fléchée qui appelle setCount pour augmenter count


### 4. Affichage de la valeur
```javascript
<p className="count-display">
  Valeur : {count}
</p>
```
Les accolades {} affichent la valeur de count


### 5. Événements onClick
```javascript
<button onClick={increment}>+ 1</button>
```
Quand on clique, la fonction increment s'exécute


### 6. Rendu conditionnel
```javascript
{count > 10 && (
  <p className="message">Wow, vous avez dépassé 10 !</p>
)}
```
Le message ne s'affiche QUE si count > 10


## FLUX D'EXÉCUTION COMPLET

```
┌─────────────────────────────────────────────────────────┐
│  1. App se rend avec count = 0                          │
│     -> Affiche "Valeur : 0"                              │
│                                                         │
│  2. Utilisateur clique sur "+ 1"                        │
│     -> onClick appelle increment()                       │
│                                                         │
│  3. increment() exécute setCount(count + 1)             │
│     -> setCount(0 + 1) = setCount(1)                     │
│                                                         │
│  4. React détecte que count a changé                    │
│     -> Planifie un nouveau rendu                         │
│                                                         │
│  5. App se re-rend avec count = 1                       │
│     -> Affiche "Valeur : 1"                              │
│     -> L'interface se met à jour automatiquement !       │
└─────────────────────────────────────────────────────────┘
```


# === EXERCICE AVANCÉ : LISTE DE TÂCHES (TODO) ===

Créez un nouveau fichier src/TodoApp.js :

```javascript
import { useState } from 'react';

function TodoApp() {
  // État pour la liste des tâches
  const [todos, setTodos] = useState([]);
  
  // État pour l'input
  const [inputValue, setInputValue] = useState('');
  
  // Ajouter une tâche
  const addTodo = () => {
    if (inputValue.trim() === '') return; // Ignorer si vide
    
    const newTodo = {
      id: Date.now(), // ID unique basé sur timestamp
      text: inputValue,
      completed: false
    };
    
    setTodos([...todos, newTodo]); // Ajouter à la liste
    setInputValue(''); // Vider l'input
  };
  
  // Basculer l'état complété d'une tâche
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id 
        ? { ...todo, completed: !todo.completed }
        : todo
    ));
  };
  
  // Supprimer une tâche
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  
  // Gérer la touche Enter
  const handleKeyPress = (e) => {
    if (e.key === 'Enter') {
      addTodo();
    }
  };
  
  return (
    <div className="todo-app">
      <h1>[NOTE] Ma Liste de Tâches</h1>
      
      {/* Zone d'ajout */}
      <div className="todo-input">
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onKeyPress={handleKeyPress}
          placeholder="Ajouter une tâche..."
        />
        <button onClick={addTodo}>Ajouter</button>
      </div>
      
      {/* Statistiques */}
      <div className="stats">
        <p>Total : {todos.length}</p>
        <p>Complétées : {todos.filter(t => t.completed).length}</p>
        <p>Restantes : {todos.filter(t => !t.completed).length}</p>
      </div>
      
      {/* Liste des tâches */}
      <ul className="todo-list">
        {todos.map(todo => (
          <li key={todo.id} className={todo.completed ? 'completed' : ''}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span>{todo.text}</span>
            <button 
              className="delete-btn"
              onClick={() => deleteTodo(todo.id)}
            >
              [X]
            </button>
          </li>
        ))}
      </ul>
      
      {/* Message si liste vide */}
      {todos.length === 0 && (
        <p className="empty-message">
          Aucune tâche pour le moment. Ajoutez-en une !
        </p>
      )}
    </div>
  );
}

export default TodoApp;
```

Modifiez src/App.js pour utiliser TodoApp :

```javascript
import TodoApp from './TodoApp';
import './App.css';

function App() {
  return (
    <div className="App">
      <TodoApp />
    </div>
  );
}

export default App;
```

Ajoutez ces styles dans src/App.css :

```css
.todo-app {
  max-width: 600px;
  margin: 50px auto;
  padding: 20px;
  background-color: #f5f5f5;
  border-radius: 10px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.todo-input {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.todo-input input {
  flex: 1;
  padding: 12px;
  font-size: 16px;
  border: 2px solid #ddd;
  border-radius: 5px;
}

.todo-input button {
  padding: 12px 24px;
  font-size: 16px;
  background-color: #28a745;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

.stats {
  display: flex;
  justify-content: space-around;
  background-color: white;
  padding: 15px;
  border-radius: 5px;
  margin-bottom: 20px;
}

.stats p {
  margin: 0;
  font-weight: bold;
}

.todo-list {
  list-style: none;
  padding: 0;
}

.todo-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 15px;
  background-color: white;
  margin-bottom: 10px;
  border-radius: 5px;
  transition: all 0.3s;
}

.todo-list li:hover {
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.todo-list li.completed span {
  text-decoration: line-through;
  color: #999;
}

.todo-list input[type="checkbox"] {
  width: 20px;
  height: 20px;
  cursor: pointer;
}

.todo-list span {
  flex: 1;
  font-size: 16px;
}

.delete-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 18px;
  padding: 5px;
}

.empty-message {
  text-align: center;
  color: #999;
  font-style: italic;
  margin-top: 30px;
}
```


## Concepts utilisés dans TodoApp

### 1. État avec tableau
```javascript
const [todos, setTodos] = useState([]);
```
État qui contient un tableau d'objets


### 2. Contrôle d'input
```javascript
<input
  value={inputValue}
  onChange={(e) => setInputValue(e.target.value)}
/>
```
Input contrôlé : sa valeur est liée à l'état


### 3. Spread operator (...)
```javascript
setTodos([...todos, newTodo]);
```
Crée un NOUVEAU tableau avec les anciens todos + le nouveau
(Immutabilité : ne jamais modifier l'ancien état)


### 4. Map pour transformation
```javascript
setTodos(todos.map(todo =>
  todo.id === id 
    ? { ...todo, completed: !todo.completed }
    : todo
));
```
- Parcourt chaque todo
- Si c'est celui qu'on cherche, on inverse completed
- Sinon, on garde le todo tel quel


### 5. Filter pour suppression
```javascript
setTodos(todos.filter(todo => todo.id !== id));
```
Garde tous les todos SAUF celui avec l'id spécifié


### 6. Key dans les listes
```javascript
{todos.map(todo => (
  <li key={todo.id}>
))}
```
CRUCIAL : React a besoin d'une key unique pour optimiser


### 7. Calculs dérivés
```javascript
{todos.filter(t => t.completed).length}
```
Calcule le nombre de tâches complétées à chaque rendu


# === CONCEPTS AVANCÉS EXPLIQUÉS SIMPLEMENT ===

## 1. Pourquoi l'immutabilité est importante

```javascript
// [X] MAUVAIS : Mutation directe
const [items, setItems] = useState([1, 2, 3]);
items.push(4);        // Modifie directement le tableau
setItems(items);      // React ne détecte PAS le changement !

// [OK] BON : Création d'un nouveau tableau
setItems([...items, 4]);  // Nouveau tableau -> React détecte !
```

POURQUOI ?
React compare les références d'objets pour savoir s'il doit re-rendre.
Si vous modifiez directement, la référence reste la même -> pas de re-rendu !


## 2. Batching des mises à jour

```javascript
function handleClick() {
  setCount(count + 1);    // count = 1
  setCount(count + 1);    // count = 1 (pas 2 !)
  console.log(count);     // Affiche 0 (pas encore mis à jour)
}
```

React GROUP les mises à jour pour la performance.

SOLUTION : Utiliser la forme fonctionnelle
```javascript
function handleClick() {
  setCount(prev => prev + 1);  // prev = 0 -> 1
  setCount(prev => prev + 1);  // prev = 1 -> 2 [OK]
}
```


## 3. Pourquoi les keys sont importantes

```javascript
// [X] MAUVAIS : Index comme key
{items.map((item, index) => (
  <Item key={index} data={item} />
))}
```

PROBLÈME : Si vous supprimez un élément au milieu, les index changent
-> React se perd et peut afficher les mauvaises données !

```javascript
// [OK] BON : ID unique et stable
{items.map(item => (
  <Item key={item.id} data={item} />
))}
```


## 4. Fermetures (Closures) et état

```javascript
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count);  // Affiche toujours 0 !
      setCount(count + 1); // Ne fonctionne pas comme prévu
    }, 1000);
    
    return () => clearInterval(timer);
  }, []);  // Dépendances vides
  
  return <div>{count}</div>;
}
```

PROBLÈME : `count` dans setInterval est "figé" à 0 (closure)

SOLUTION :
```javascript
setCount(prev => prev + 1);  // [OK] Toujours correct
```


# === DEBUGGING : ERREURS COURANTES ===

## Erreur 1 : "Cannot read property of undefined"

```javascript
function UserProfile({ user }) {
  return <div>{user.name}</div>;  // [X] Erreur si user = null
}
```

SOLUTIONS :
```javascript
// Solution 1 : Vérification conditionnelle
if (!user) return <div>Loading...</div>;
return <div>{user.name}</div>;

// Solution 2 : Optional chaining
return <div>{user?.name}</div>;

// Solution 3 : Valeur par défaut
return <div>{user?.name || 'Anonyme'}</div>;
```


## Erreur 2 : "Maximum update depth exceeded"

```javascript
function Component() {
  const [count, setCount] = useState(0);
  
  // [X] Appelle setCount à chaque rendu -> boucle infinie !
  setCount(count + 1);
  
  return <div>{count}</div>;
}
```

SOLUTION : Mettre dans un événement ou useEffect
```javascript
// [OK] Dans un événement
<button onClick={() => setCount(count + 1)}>+</button>

// [OK] Dans useEffect
useEffect(() => {
  setCount(count + 1);
}, []); // S'exécute une seule fois
```


## Erreur 3 : "Each child should have a unique key"

```javascript
// [X] Pas de key
{items.map(item => <div>{item}</div>)}

// [OK] Avec key unique
{items.map(item => <div key={item.id}>{item.text}</div>)}
```


## Erreur 4 : "Invalid hook call"

```javascript
// [X] Hook dans une condition
if (condition) {
  const [state, setState] = useState(0);
}

// [X] Hook dans une boucle
for (let i = 0; i < 5; i++) {
  const [state, setState] = useState(0);
}
```

RÈGLE : Les Hooks DOIVENT être appelés au niveau supérieur
```javascript
// [OK] Correct
function Component() {
  const [state, setState] = useState(0);
  
  if (condition) {
    // Utiliser state ici
  }
}
```


# === RESSOURCES POUR APPRENDRE ===

## Documentation officielle
https://react.dev/learn
-> Le meilleur endroit pour apprendre, avec tutoriels interactifs


## Tutoriels recommandés

1. **React Tutorial officiel (Tic-Tac-Toe)**
   https://react.dev/learn/tutorial-tic-tac-toe
   Créez un jeu de morpion en apprenant React

2. **FreeCodeCamp - React Course**
   https://www.freecodecamp.org/learn/front-end-libraries/react/
   Cours gratuit avec exercices pratiques

3. **Codecademy - Learn React**
   https://www.codecademy.com/learn/react-101
   Cours interactif pour débutants


## Outils utiles

1. **React DevTools** (Extension navigateur)
   - Chrome : https://chrome.google.com/webstore/detail/react-developer-tools
   - Firefox : https://addons.mozilla.org/en-US/firefox/addon/react-devtools/
   -> Permet d'inspecter composants, props, state

2. **CodeSandbox** (Éditeur en ligne)
   https://codesandbox.io/
   -> Testez React directement dans le navigateur

3. **StackBlitz** (Éditeur en ligne)
   https://stackblitz.com/
   -> Comme CodeSandbox, très rapide


## Communautés

1. **Reddit r/reactjs**
   https://reddit.com/r/reactjs
   -> Poser des questions, voir des projets

2. **Discord React**
   https://discord.gg/react
   -> Chat en temps réel avec d'autres développeurs

3. **Stack Overflow**
   https://stackoverflow.com/questions/tagged/reactjs
   -> Questions/réponses techniques


# === PROCHAINES ÉTAPES ===

Maintenant que vous comprenez les bases, voici l'ordre d'apprentissage :

```
1. [OK] Concepts fondamentaux (ce fichier)
   └─ Composants, JSX, Props, State

2. -> Hooks de base
   └─ useState, useEffect
   └─ Fichier: react.txt (sections HOOKS)

3. -> Gestion de formulaires
   └─ Inputs contrôlés, validation
   └─ Fichier: react.txt (section FORMULAIRES)

4. -> Requêtes API
   └─ fetch, useEffect, async/await
   └─ Fichier: react.txt (section API BACKEND)

5. -> Routing (navigation entre pages)
   └─ React Router
   └─ Fichier: react.txt (section ROUTING)

6. -> State management (apps complexes)
   └─ Context API, Redux
   └─ Fichier: react.txt (section STATE MANAGEMENT)

7. -> Hooks avancés
   └─ useCallback, useMemo, useRef
   └─ Fichier: react.txt (sections HOOKS avancés)

8. -> Performance et optimisation
   └─ React.memo, lazy loading
   └─ Fichier: react.txt (section PERFORMANCE)
```


# === PROJET FIL ROUGE : CRÉER UNE APP COMPLÈTE ===

Pour consolider vos connaissances, créez cette application pas à pas :

## Application : Gestionnaire de Notes

FONCTIONNALITÉS :
- Créer, éditer, supprimer des notes
- Catégories (Travail, Personnel, Urgent)
- Recherche de notes
- Sauvegarde dans localStorage

COMPÉTENCES UTILISÉES :
[OK] Composants
[OK] State avec useState
[OK] Props
[OK] Listes et keys
[OK] Événements
[OK] Formulaires
[OK] Rendu conditionnel
[OK] localStorage (persistance)


## Étape 1 : Structure de base

```javascript
// src/NotesApp.js
import { useState } from 'react';

function NotesApp() {
  const [notes, setNotes] = useState([]);
  const [searchTerm, setSearchTerm] = useState('');
  
  return (
    <div className="notes-app">
      <h1>[CARNET] Mon Carnet de Notes</h1>
      
      <SearchBar 
        searchTerm={searchTerm}
        onSearch={setSearchTerm}
      />
      
      <AddNoteForm onAdd={addNote} />
      
      <NotesList 
        notes={filteredNotes}
        onEdit={editNote}
        onDelete={deleteNote}
      />
    </div>
  );
}
```


## Étape 2 : Implémenter les composants

Créez chaque composant un par un :
- SearchBar : Barre de recherche
- AddNoteForm : Formulaire d'ajout
- NotesList : Liste des notes
- NoteItem : Une note individuelle

Ajoutez les fonctions :
- addNote() : Ajouter une note
- editNote() : Modifier une note
- deleteNote() : Supprimer une note
- filteredNotes : Filtrer selon recherche


## Étape 3 : Ajouter la persistance

```javascript
// Sauvegarder dans localStorage à chaque changement
useEffect(() => {
  localStorage.setItem('notes', JSON.stringify(notes));
}, [notes]);

// Charger au démarrage
useEffect(() => {
  const saved = localStorage.getItem('notes');
  if (saved) {
    setNotes(JSON.parse(saved));
  }
}, []);
```


# === CONCLUSION ===

Félicitations ! Vous comprenez maintenant :
[OK] Qu'est-ce que React et pourquoi l'utiliser
[OK] Les composants et comment les créer
[OK] Le JSX et sa syntaxe
[OK] L'état (state) et useState
[OK] Les props et la communication parent-enfant
[OK] Le flux unidirectionnel des données
[OK] Le Virtual DOM et les performances

PROCHAINES LECTURES :
-> react.txt - sections HOOKS pour approfondir useState et useEffect
-> react.txt - section FORMULAIRES pour gérer les inputs complexes
-> react.txt - section API BACKEND pour communiquer avec un serveur


CONSEIL FINAL :
La meilleure façon d'apprendre React est de PRATIQUER !
Créez des petits projets, faites des erreurs, déboguez, recommencez.
C'est comme apprendre à faire du vélo : la théorie aide, mais il faut pédaler !


[OK] BASES - COMPOSANTS - EXPLICATION DÉTAILLÉE


# === QU'EST-CE QU'UN COMPOSANT ? ===

Un composant React = Une fonction JavaScript qui retourne du JSX (HTML-like)

## Analogie Simple

COMPOSANT = MOULE À GÂTEAU
- Vous créez un moule (le composant) une seule fois
- Vous pouvez faire des dizaines de gâteaux (utiliser le composant partout)
- Chaque gâteau peut avoir des ingrédients différents (props différentes)
- Mais la forme reste la même (structure du composant)

```javascript
// Le MOULE (composant)
function Button({ text, color }) {
  return <button style={{ backgroundColor: color }}>{text}</button>;
}

// Faire des GÂTEAUX (utiliser le composant)
<Button text="Valider" color="green" />    // Gâteau vert
<Button text="Annuler" color="red" />      // Gâteau rouge
<Button text="Modifier" color="blue" />    // Gâteau bleu
```


## Pourquoi utiliser des composants ?

### SANS composants (répétition de code)

```html
<!-- Page 1 -->
<button style="background: green; padding: 10px;">Valider</button>

<!-- Page 2 -->
<button style="background: green; padding: 10px;">Confirmer</button>

<!-- Page 3 -->
<button style="background: green; padding: 10px;">OK</button>
```

PROBLÈMES :
[X] Code répété 3 fois
[X] Si on veut changer le style, il faut modifier 3 endroits
[X] Risque d'oublier un endroit
[X] Difficile à maintenir


### AVEC composants (réutilisation)

```javascript
// Définir UNE FOIS
function GreenButton({ text }) {
  return (
    <button style={{ background: 'green', padding: '10px' }}>
      {text}
    </button>
  );
}

// Utiliser PARTOUT
<GreenButton text="Valider" />
<GreenButton text="Confirmer" />
<GreenButton text="OK" />
```

AVANTAGES :
[OK] Code écrit une seule fois
[OK] Pour changer le style, on modifie UN seul endroit
[OK] Cohérence garantie dans toute l'application
[OK] Facile à tester et à maintenir



# === COMPOSANTS FONCTIONNELS (MODERNE - RECOMMANDÉ) ===

Depuis React 16.8 (2019), les composants fonctionnels sont la norme.


## Le Composant Le Plus Simple Possible

```javascript
function Welcome() {
  return <h1>Bonjour !</h1>;
}
```

DÉCOMPOSITION :
```javascript
function Welcome()           // 1. Nom du composant (MAJUSCULE obligatoire)
{                            // 2. Début de la fonction
  return <h1>Bonjour !</h1>; // 3. Retourne du JSX
}                            // 4. Fin de la fonction
```

UTILISATION :
```javascript
// Dans un autre composant
function App() {
  return (
    <div>
      <Welcome />    {/* Comme une balise HTML auto-fermante */}
    </div>
  );
}
```


## Règles de Nommage (TRÈS IMPORTANT)

```javascript
// [OK] CORRECT : Commence par MAJUSCULE
function Welcome() { }
function UserProfile() { }
function MyButton() { }

// [X] INCORRECT : Commence par minuscule
function welcome() { }      // React pense que c'est du HTML !
function userProfile() { }  // Ne fonctionnera pas !
```

POURQUOI LA MAJUSCULE ?
React distingue les composants des balises HTML :
- `<button>` = Balise HTML native
- `<Button>` = Composant React personnalisé

```javascript
<div>
  <button>Je suis du HTML</button>
  <Button>Je suis un composant React</Button>
</div>
```


## Composant avec du Contenu Plus Complexe

```javascript
function UserCard() {
  return (
    <div className="card">
      <img src="avatar.jpg" alt="Avatar" />
      <h2>John Doe</h2>
      <p>Développeur React</p>
      <button>Contacter</button>
    </div>
  );
}
```

EXPLICATION :
- Un seul élément racine : `<div className="card">`
- Plusieurs éléments enfants à l'intérieur
- Le composant retourne TOUT le bloc JSX


## Composant avec Parenthèses (Multi-lignes)

```javascript
// [OK] CORRECT : Parenthèses pour JSX multi-lignes
function UserCard() {
  return (
    <div>
      <h1>Titre</h1>
      <p>Paragraphe</p>
    </div>
  );
}

// [X] INCORRECT : Sans parenthèses, JavaScript interprète mal
function UserCard() {
  return
    <div>
      <h1>Titre</h1>
    </div>
  ;
}
// JavaScript lit : return; (puis du code mort après)
```

RÈGLE : Si le JSX fait plusieurs lignes, TOUJOURS mettre des parenthèses !


## Composant avec Fragment (pas de div inutile)

```javascript
// [X] PROBLÈME : div inutile dans le DOM
function TwoElements() {
  return (
    <div>
      <h1>Titre</h1>
      <p>Paragraphe</p>
    </div>
  );
}

// Résultat HTML :
// <div>  <- div inutile !
//   <h1>Titre</h1>
//   <p>Paragraphe</p>
// </div>


// [OK] SOLUTION : Fragment (<> </>)
function TwoElements() {
  return (
    <>
      <h1>Titre</h1>
      <p>Paragraphe</p>
    </>
  );
}

// Résultat HTML :
// <h1>Titre</h1>
// <p>Paragraphe</p>
// (pas de div wrapper !)
```

QUAND UTILISER Fragment ?
- Quand vous devez retourner plusieurs éléments
- Quand ajouter une div casserait votre CSS
- Quand vous voulez un DOM propre


## Fragment avec Key (pour les listes)

```javascript
function List({ items }) {
  return (
    <>
      {items.map(item => (
        <React.Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </React.Fragment>
      ))}
    </>
  );
}
```

POURQUOI React.Fragment au lieu de <> ?
- La syntaxe <> ne permet PAS d'ajouter des attributs
- Pour mettre une `key`, il faut utiliser `<React.Fragment>`



# === COMPOSANTS AVEC PROPS (PARAMÈTRES) ===

Props = Properties = Paramètres passés à un composant


## Props : Concept de Base

```javascript
// PYTHON - Fonction avec paramètres
def greet(name, age):
    return f"Bonjour {name}, tu as {age} ans"

greet("Alice", 25)  # Appel avec arguments


// REACT - Composant avec props
function Greet(props) {
  return <p>Bonjour {props.name}, tu as {props.age} ans</p>;
}

<Greet name="Alice" age={25} />  {/* Utilisation avec props */}
```


## Anatomie Complète des Props

```javascript
function Welcome(props) {
  //          ^
  //          props = objet JavaScript contenant toutes les props
  
  console.log(props);
  // Affiche : { name: "Alice", age: 25 }
  
  return (
    <div>
      <h1>Bonjour, {props.name} !</h1>
      <p>Tu as {props.age} ans</p>
    </div>
  );
}

// Utilisation
<Welcome name="Alice" age={25} />
//       ^            ^
//       props.name   props.age
```

EXPLICATION :
1. `<Welcome name="Alice" age={25} />` crée un objet :
   ```javascript
   { name: "Alice", age: 25 }
   ```

2. React passe cet objet comme argument `props` à la fonction

3. Vous accédez aux valeurs avec `props.name`, `props.age`


## Types de Props

### String (entre guillemets)
```javascript
<User name="Alice" />
//    ^ String, guillemets
```

### Nombre (entre accolades)
```javascript
<User age={25} />
//    ^ Nombre, accolades
```

### Boolean (entre accolades)
```javascript
<User isAdmin={true} />
<User isActive={false} />

// Raccourci : prop seule = true
<User isAdmin />  {/* Équivalent à isAdmin={true} */}
```

### Tableau (entre accolades)
```javascript
<User hobbies={['React', 'JavaScript', 'CSS']} />
```

### Objet (entre accolades)
```javascript
<User address={{ street: 'Rue de la Paix', city: 'Paris' }} />
//            ^ Attention : double accolades !
//            { } = JSX, { } = objet JavaScript
```

### Fonction (entre accolades)
```javascript
<Button onClick={() => alert('Cliqué !')} />
<Button onClick={handleClick} />
```


## Destructuring des Props (MODERNE ET PROPRE)

Au lieu d'écrire `props.name` partout, on peut destructurer :

### AVANT (verbeux)
```javascript
function Welcome(props) {
  return (
    <div>
      <h1>Bonjour, {props.name} !</h1>
      <p>Tu as {props.age} ans</p>
      <p>Email : {props.email}</p>
    </div>
  );
}
```

### APRÈS (destructuring)
```javascript
function Welcome({ name, age, email }) {
  //             ^ Extraction directe des props
  
  return (
    <div>
      <h1>Bonjour, {name} !</h1>
      <p>Tu as {age} ans</p>
      <p>Email : {email}</p>
    </div>
  );
}
```

AVANTAGES :
[OK] Plus lisible : `{name}` au lieu de `{props.name}`
[OK] Montre clairement quelles props sont utilisées
[OK] C'est le standard dans la communauté React


## Destructuring avec Valeurs par Défaut

```javascript
function Button({ text = "Cliquez ici", color = "blue" }) {
  return (
    <button style={{ backgroundColor: color }}>
      {text}
    </button>
  );
}

// Utilisation
<Button />                          // Utilise les valeurs par défaut
<Button text="Valider" />           // color = "blue" (défaut)
<Button text="Annuler" color="red" /> // Tout personnalisé
```


## Destructuring Partiel (props restantes)

```javascript
function Button({ text, ...otherProps }) {
  //                    ^ ...otherProps = toutes les autres props
  
  return <button {...otherProps}>{text}</button>;
}

// Utilisation
<Button 
  text="Cliquez" 
  className="btn" 
  disabled={true}
  data-testid="main-btn"
/>

// otherProps = { className: "btn", disabled: true, data-testid: "main-btn" }
// Toutes les props sont passées au <button>
```


## Props avec Noms Composés

```javascript
function UserProfile({ firstName, lastName, emailAddress }) {
  return (
    <div>
      <h1>{firstName} {lastName}</h1>
      <p>{emailAddress}</p>
    </div>
  );
}

// Utilisation
<UserProfile 
  firstName="John"
  lastName="Doe"
  emailAddress="john@example.com"
/>
```


## Props Children (SPÉCIAL ET IMPORTANT)

`children` = Prop spéciale pour le contenu ENTRE les balises

```javascript
// Composant qui utilise children
function Card({ title, children }) {
  //                   ^ Prop spéciale
  
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">
        {children}  {/* Affiche le contenu entre <Card>...</Card> */}
      </div>
    </div>
  );
}

// Utilisation
<Card title="Mon Titre">
  <p>Ceci est le contenu de la carte.</p>
  <button>Cliquer</button>
  <img src="photo.jpg" alt="Photo" />
</Card>
```

RÉSULTAT :
```html
<div class="card">
  <h2>Mon Titre</h2>
  <div class="card-body">
    <!-- Tout ce qui est entre <Card> et </Card> -->
    <p>Ceci est le contenu de la carte.</p>
    <button>Cliquer</button>
    <img src="photo.jpg" alt="Photo" />
  </div>
</div>
```

POURQUOI children EST PUISSANT ?
- Permet de créer des composants "conteneurs"
- Comme une boîte : vous décidez ce que vous mettez dedans
- Réutilisable pour n'importe quel contenu


## Exemple Complet : Carte Utilisateur

```javascript
function UserCard({ name, email, avatar, isOnline, onContact }) {
  //             ^ Destructuring de toutes les props
  
  return (
    <div className="user-card">
      {/* Image avatar */}
      <img 
        src={avatar} 
        alt={`Avatar de ${name}`}
        className="avatar"
      />
      
      {/* Informations */}
      <div className="user-info">
        <h3>{name}</h3>
        <p>{email}</p>
        
        {/* Badge conditionnel */}
        {isOnline && (
          <span className="badge online">En ligne</span>
        )}
        
        {!isOnline && (
          <span className="badge offline">Hors ligne</span>
        )}
      </div>
      
      {/* Bouton d'action */}
      <button onClick={onContact}>
        Contacter
      </button>
    </div>
  );
}

// Utilisation dans un autre composant
function UserList() {
  const handleContact = (userName) => {
    alert(`Contacter ${userName}`);
  };
  
  return (
    <div>
      <UserCard
        name="Alice"
        email="alice@example.com"
        avatar="/images/alice.jpg"
        isOnline={true}
        onContact={() => handleContact("Alice")}
      />
      
      <UserCard
        name="Bob"
        email="bob@example.com"
        avatar="/images/bob.jpg"
        isOnline={false}
        onContact={() => handleContact("Bob")}
      />
    </div>
  );
}
```

CONCEPTS UTILISÉS :
[OK] Destructuring des props
[OK] Props de différents types (string, boolean, fonction)
[OK] Rendu conditionnel avec &&
[OK] Template literals dans les attributs
[OK] Fonctions handlers passées en props



# === ARROW FUNCTIONS (SYNTAXE ALTERNATIVE) ===

Les Arrow Functions (fonctions fléchées) sont une syntaxe moderne JavaScript.


## Syntaxe Classique vs Arrow Function

```javascript
// SYNTAXE CLASSIQUE
function Welcome() {
  return <h1>Bonjour !</h1>;
}

// ARROW FUNCTION (syntaxe moderne)
const Welcome = () => {
  return <h1>Bonjour !</h1>;
};
```


## Arrow Function avec Return Implicite

```javascript
// Avec return explicite
const Welcome = () => {
  return <h1>Bonjour !</h1>;
};

// Avec return implicite (sans accolades)
const Welcome = () => <h1>Bonjour !</h1>;
//                   ^ Pas d'accolades = return automatique
```

RÈGLE :
- Si le corps fait UNE SEULE LIGNE -> peut omettre `return` et `{ }`
- Si le corps fait PLUSIEURS LIGNES -> besoin de `{ }` et `return`


## Arrow Function avec Props

```javascript
// Destructuring dans les paramètres
const Greet = ({ name, age }) => {
  return <p>Bonjour {name}, tu as {age} ans</p>;
};

// Avec return implicite
const Greet = ({ name, age }) => (
  <p>Bonjour {name}, tu as {age} ans</p>
);
//                               ^ Parenthèses pour JSX multi-lignes


// Encore plus court (une seule ligne)
const Greet = ({ name }) => <p>Bonjour {name} !</p>;
```


## Quand Utiliser Quoi ?

```javascript
// [OK] FONCTION CLASSIQUE
// - Plus facile à debugger (nom de fonction visible)
// - Plus familier pour les débutants
function UserProfile() {
  return <div>...</div>;
}


// [OK] ARROW FUNCTION
// - Plus concise
// - Standard dans le code moderne
// - Pas de problème de 'this' (avancé)
const UserProfile = () => {
  return <div>...</div>;
};


// [OK] ARROW FUNCTION COURTE
// - Composants très simples
// - Une seule ligne de JSX
const Avatar = ({ src }) => <img src={src} alt="Avatar" />;
```

CONSEIL POUR DÉBUTANTS :
Commencez avec la syntaxe `function`, c'est plus clair.
Passez aux arrow functions quand vous êtes à l'aise.



# === VALIDATION DES PROPS (PropTypes) ===

PropTypes = Système de validation pour vérifier que les props sont du bon type


## Pourquoi Valider les Props ?

```javascript
function UserCard({ name, age, email }) {
  return (
    <div>
      <h1>{name}</h1>
      <p>Age : {age}</p>
      <p>Email : {email}</p>
    </div>
  );
}

// [X] PROBLÈME : Utilisation incorrecte
<UserCard name={123} age="vingt-cinq" />
//        ^ nombre         ^ string
//        devrait être     devrait être
//        string           nombre
```

SANS PropTypes :
- Pas d'avertissement
- Bugs silencieux
- Difficile à débugger


## Installation de PropTypes

```bash
npm install prop-types
```


## Utilisation Basique

```javascript
import PropTypes from 'prop-types';

function Welcome({ name, age, email }) {
  return (
    <div>
      <h1>Bonjour, {name} !</h1>
      <p>Age : {age}</p>
      <p>Email : {email}</p>
    </div>
  );
}

// Définir les types attendus
Welcome.propTypes = {
  name: PropTypes.string.isRequired,  // String, obligatoire
  age: PropTypes.number,               // Number, optionnel
  email: PropTypes.string              // String, optionnel
};

// Valeurs par défaut (pour props optionnelles)
Welcome.defaultProps = {
  age: 18,
  email: 'unknown@example.com'
};
```

EXPLICATION :
- `.isRequired` = Prop obligatoire, warning si absente
- Sans `.isRequired` = Prop optionnelle
- `defaultProps` = Valeurs utilisées si prop non fournie


## Tous les Types PropTypes

```javascript
import PropTypes from 'prop-types';

ComponentName.propTypes = {
  // Types primitifs
  string: PropTypes.string,
  number: PropTypes.number,
  boolean: PropTypes.bool,
  function: PropTypes.func,
  object: PropTypes.object,
  array: PropTypes.array,
  symbol: PropTypes.symbol,
  
  // Valeur spécifique
  specificValue: PropTypes.oneOf(['red', 'blue', 'green']),
  
  // Un type parmi plusieurs
  multiType: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number
  ]),
  
  // Tableau d'un type spécifique
  arrayOfNumbers: PropTypes.arrayOf(PropTypes.number),
  
  // Objet avec structure définie
  user: PropTypes.shape({
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    email: PropTypes.string
  }),
  
  // Objet avec valeurs d'un type spécifique
  scores: PropTypes.objectOf(PropTypes.number),
  
  // Élément React
  element: PropTypes.element,
  
  // Instance d'une classe
  instance: PropTypes.instanceOf(Date),
  
  // Fonction custom de validation
  customValidation: function(props, propName, componentName) {
    if (props[propName] < 0) {
      return new Error(
        `Prop ${propName} dans ${componentName} doit être positif`
      );
    }
  }
};
```


## Exemple Complet avec PropTypes

```javascript
import PropTypes from 'prop-types';

function UserCard({ name, age, email, hobbies, address, isActive, onContact }) {
  return (
    <div className={`user-card ${isActive ? 'active' : ''}`}>
      <h2>{name}</h2>
      <p>Age : {age}</p>
      <p>Email : {email}</p>
      
      <h3>Hobbies :</h3>
      <ul>
        {hobbies.map((hobby, index) => (
          <li key={index}>{hobby}</li>
        ))}
      </ul>
      
      <p>Ville : {address.city}</p>
      
      <button onClick={onContact}>Contacter</button>
    </div>
  );
}

// Validation des props
UserCard.propTypes = {
  // Strings
  name: PropTypes.string.isRequired,
  email: PropTypes.string.isRequired,
  
  // Number
  age: PropTypes.number,
  
  // Array of strings
  hobbies: PropTypes.arrayOf(PropTypes.string),
  
  // Object avec structure définie
  address: PropTypes.shape({
    street: PropTypes.string,
    city: PropTypes.string.isRequired,
    zipCode: PropTypes.string
  }).isRequired,
  
  // Boolean
  isActive: PropTypes.bool,
  
  // Function
  onContact: PropTypes.func.isRequired
};

// Valeurs par défaut
UserCard.defaultProps = {
  age: 0,
  hobbies: [],
  isActive: false
};

// Utilisation
<UserCard
  name="Alice"
  email="alice@example.com"
  age={25}
  hobbies={['React', 'JavaScript']}
  address={{ city: 'Paris', street: 'Rue de la Paix' }}
  isActive={true}
  onContact={() => alert('Contacter Alice')}
/>
```


## PropTypes vs TypeScript

```javascript
// PROPTYPES (JavaScript)
function Button({ text, onClick }) {
  return <button onClick={onClick}>{text}</button>;
}

Button.propTypes = {
  text: PropTypes.string.isRequired,
  onClick: PropTypes.func.isRequired
};


// TYPESCRIPT (typage fort)
interface ButtonProps {
  text: string;
  onClick: () => void;
}

const Button: React.FC<ButtonProps> = ({ text, onClick }) => {
  return <button onClick={onClick}>{text}</button>;
};
```

DIFFÉRENCES :
- **PropTypes** : Vérification à l'EXÉCUTION (runtime)
  - Warnings dans la console
  - Ne bloque pas le code

- **TypeScript** : Vérification à la COMPILATION
  - Erreurs avant d'exécuter
  - Autocomplétion dans l'éditeur
  - Plus robuste pour gros projets



# === COMPOSITION DE COMPOSANTS ===

Composition = Assembler des petits composants pour en faire des plus grands


## Principe de Composition

```javascript
// Composants atomiques (petits)
function Avatar({ src, alt }) {
  return <img src={src} alt={alt} className="avatar" />;
}

function UserName({ name }) {
  return <h3 className="user-name">{name}</h3>;
}

function UserEmail({ email }) {
  return <p className="user-email">{email}</p>;
}

// Composant composé (assemble les petits)
function UserProfile({ user }) {
  return (
    <div className="user-profile">
      <Avatar src={user.avatar} alt={user.name} />
      <UserName name={user.name} />
      <UserEmail email={user.email} />
    </div>
  );
}

// Utilisation
const user = {
  name: 'Alice',
  email: 'alice@example.com',
  avatar: '/images/alice.jpg'
};

<UserProfile user={user} />
```

AVANTAGES :
[OK] Chaque composant a UNE responsabilité
[OK] Facile à tester individuellement
[OK] Réutilisable séparément
[OK] Code plus lisible


## Composition vs Héritage

React favorise la COMPOSITION plutôt que l'héritage de classes.

```javascript
// [X] HÉRITAGE (approche orientée objet classique)
class Button extends BaseButton {
  // Pas recommandé en React
}


// [OK] COMPOSITION (approche React)
function PrimaryButton({ children, ...props }) {
  return (
    <Button {...props} className="btn-primary">
      {children}
    </Button>
  );
}

function DangerButton({ children, ...props }) {
  return (
    <Button {...props} className="btn-danger">
      {children}
    </Button>
  );
}
```


## Exemple Avancé : Layout avec Composition

```javascript
// Composants de base
function Header({ children }) {
  return <header className="header">{children}</header>;
}

function Sidebar({ children }) {
  return <aside className="sidebar">{children}</aside>;
}

function MainContent({ children }) {
  return <main className="main-content">{children}</main>;
}

function Footer({ children }) {
  return <footer className="footer">{children}</footer>;
}

// Layout composé
function PageLayout({ children }) {
  return (
    <div className="page-layout">
      <Header>
        <nav>
          <a href="/">Accueil</a>
          <a href="/about">À propos</a>
        </nav>
      </Header>
      
      <div className="page-body">
        <Sidebar>
          <ul>
            <li>Menu 1</li>
            <li>Menu 2</li>
          </ul>
        </Sidebar>
        
        <MainContent>
          {children}  {/* Contenu de la page */}
        </MainContent>
      </div>
      
      <Footer>
        <p>© 2024 Mon Site</p>
      </Footer>
    </div>
  );
}

// Utilisation pour différentes pages
function HomePage() {
  return (
    <PageLayout>
      <h1>Page d'accueil</h1>
      <p>Bienvenue sur mon site !</p>
    </PageLayout>
  );
}

function AboutPage() {
  return (
    <PageLayout>
      <h1>À propos</h1>
      <p>Informations sur nous.</p>
    </PageLayout>
  );
}
```



# === COMPOSANTS CLASSE (LEGACY - ANCIEN STYLE) ===

[ATTENTION] IMPORTANT : Les composants classe ne sont plus recommandés depuis React 16.8 (2019).
Les Hooks ont remplacé les classes pour gérer l'état et les effets.

POURQUOI LES CONNAÎTRE QUAND MÊME ?
- Beaucoup de code existant utilise des classes
- Vous les verrez dans d'anciens tutoriels
- Certaines entreprises ont encore du code legacy


## Composant Classe Basique

```javascript
import React, { Component } from 'react';

class Welcome extends Component {
  render() {
    return <h1>Bonjour !</h1>;
  }
}
```

COMPARAISON :
```javascript
// FONCTIONNEL (moderne)
function Welcome() {
  return <h1>Bonjour !</h1>;
}

// CLASSE (ancien)
class Welcome extends Component {
  render() {
    return <h1>Bonjour !</h1>;
  }
}
```


## Composant Classe avec Props

```javascript
class Welcome extends Component {
  render() {
    return (
      <div>
        <h1>Bonjour, {this.props.name} !</h1>
        <p>Tu as {this.props.age} ans</p>
      </div>
    );
  }
}

// Utilisation (identique)
<Welcome name="Alice" age={25} />
```

DIFFÉRENCE CLÉ : `this.props` au lieu de juste `props`


## Composant Classe avec State

```javascript
class Counter extends Component {
  constructor(props) {
    super(props);  // OBLIGATOIRE si on utilise constructor
    
    // Initialiser l'état
    this.state = {
      count: 0
    };
  }
  
  // Méthode pour incrémenter
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }
  
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>+1</button>
      </div>
    );
  }
}
```

COMPARAISON AVEC FONCTIONNEL :
```javascript
// FONCTIONNEL (moderne et plus court)
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

// CLASSE (ancien, plus verbeux)
class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }
  
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>+1</button>
      </div>
    );
  }
}
```

POURQUOI LES FONCTIONS SONT MEILLEURES :
[OK] Moins de code (pas de constructor, pas de render(), pas de this)
[OK] Plus facile à comprendre
[OK] Hooks plus puissants que lifecycle methods
[OK] Meilleure performance


## Problème du 'this' dans les Classes

```javascript
class Button extends Component {
  constructor(props) {
    super(props);
    this.state = { clicked: false };
    
    // [X] OUBLIER DE BINDER = BUG !
    // this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    // [X] this sera undefined !
    this.setState({ clicked: true });
  }
  
  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}
```

SOLUTIONS :
```javascript
// Solution 1 : Binder dans constructor
constructor(props) {
  super(props);
  this.handleClick = this.handleClick.bind(this);
}

// Solution 2 : Arrow function dans render (crée nouvelle fonction chaque fois)
<button onClick={() => this.handleClick()}>Click</button>

// Solution 3 : Arrow function pour la méthode (MEILLEUR)
handleClick = () => {
  this.setState({ clicked: true });
}
```

AVEC FONCTIONS : Pas de problème de `this` ! [BRAVO]


## Lifecycle Methods (Cycle de Vie)

```javascript
class UserProfile extends Component {
  constructor(props) {
    super(props);
    this.state = { user: null };
  }
  
  // 1. Composant monté (après premier render)
  componentDidMount() {
    console.log('Composant monté');
    this.fetchUser();
  }
  
  // 2. Composant mis à jour (après chaque re-render)
  componentDidUpdate(prevProps, prevState) {
    console.log('Composant mis à jour');
    
    // Éviter boucle infinie : vérifier si props ont changé
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser();
    }
  }
  
  // 3. Composant démonté (avant destruction)
  componentWillUnmount() {
    console.log('Composant démonté');
    // Nettoyage : annuler timers, requêtes, etc.
  }
  
  fetchUser() {
    fetch(`/api/users/${this.props.userId}`)
      .then(res => res.json())
      .then(user => this.setState({ user }));
  }
  
  render() {
    if (!this.state.user) return <div>Loading...</div>;
    return <div>{this.state.user.name}</div>;
  }
}
```

ÉQUIVALENT AVEC HOOKS :
```javascript
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    // componentDidMount + componentDidUpdate
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(user => setUser(user));
    
    // componentWillUnmount (cleanup)
    return () => {
      console.log('Cleanup');
    };
  }, [userId]); // Re-exécute si userId change
  
  if (!user) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}
```

HOOKS = PLUS SIMPLE ET PLUS LISIBLE !



# === ORGANISATION DES COMPOSANTS ===

Comment structurer vos composants dans un projet ?


## Structure de Fichiers Recommandée

```
src/
├── components/
│   ├── Button/
│   │   ├── Button.js          # Composant
│   │   ├── Button.css         # Styles
│   │   ├── Button.test.js     # Tests
│   │   └── index.js           # Export
│   │
│   ├── UserCard/
│   │   ├── UserCard.js
│   │   ├── UserCard.css
│   │   ├── UserCard.test.js
│   │   └── index.js
│   │
│   └── common/                 # Composants réutilisables
│       ├── Button/
│       ├── Input/
│       └── Modal/
│
├── pages/                      # Pages de l'application
│   ├── HomePage.js
│   ├── AboutPage.js
│   └── UserPage.js
│
└── App.js                      # Composant racine
```


## Exemple : Fichier Button/index.js

```javascript
// Button/Button.js
import './Button.css';

export function Button({ text, variant = 'primary', onClick }) {
  return (
    <button 
      className={`btn btn-${variant}`}
      onClick={onClick}
    >
      {text}
    </button>
  );
}

export default Button;
```

```javascript
// Button/index.js (facilite l'import)
export { default } from './Button';
export * from './Button';
```

```javascript
// Utilisation dans un autre fichier
import Button from 'components/Button';
// Au lieu de : import Button from 'components/Button/Button';
```


## Règle de Un Composant = Un Fichier

```javascript
// [X] MAUVAIS : Plusieurs composants dans un fichier
// components.js
export function Button() { }
export function Input() { }
export function Form() { }

// [OK] BON : Un composant par fichier
// Button.js
export default function Button() { }

// Input.js
export default function Input() { }

// Form.js
export default function Form() { }
```

SAUF : Petits composants helpers utilisés seulement dans un composant parent
```javascript
// UserCard.js
function Avatar({ src }) {
  return <img src={src} className="avatar" />;
}

function UserName({ name }) {
  return <h3>{name}</h3>;
}

// Composant principal exporté
export default function UserCard({ user }) {
  return (
    <div>
      <Avatar src={user.avatar} />
      <UserName name={user.name} />
    </div>
  );
}
```


## Nommage des Fichiers

```
[OK] RECOMMANDÉ : PascalCase (comme le composant)
Button.js
UserCard.js
HomePage.js

[OK] ACCEPTABLE : kebab-case
button.js
user-card.js
home-page.js

[X] ÉVITER : camelCase (confond avec variables)
userCard.js
```


## Exports Named vs Default

```javascript
// EXPORT DEFAULT (un seul export principal)
// Button.js
export default function Button() { }

// Import
import Button from './Button';
import MonBouton from './Button';  // Peut renommer


// EXPORT NAMED (plusieurs exports)
// utils.js
export function formatDate() { }
export function formatPrice() { }

// Import
import { formatDate, formatPrice } from './utils';
import { formatDate as fd } from './utils';  // Alias
```

CONSEIL : 
- Export DEFAULT pour composants
- Export NAMED pour utilitaires/helpers



# === BONNES PRATIQUES ===

## 1. Composants Purs (Pure Components)

Un composant pur = Même props -> Même résultat (pas d'effets secondaires)

```javascript
// [OK] PUR : Toujours le même résultat
function Greeting({ name }) {
  return <h1>Bonjour, {name} !</h1>;
}

// [X] IMPUR : Résultat différent à chaque appel
function Greeting({ name }) {
  return <h1>Bonjour, {name} ! {Math.random()}</h1>;
}

// [X] IMPUR : Modifie quelque chose en dehors
let globalCounter = 0;
function Greeting({ name }) {
  globalCounter++;  // Effet secondaire !
  return <h1>Bonjour, {name} !</h1>;
}
```

POURQUOI C'EST IMPORTANT ?
- Prévisible et testable
- React peut optimiser les composants purs
- Évite les bugs


## 2. Principe de Responsabilité Unique

Chaque composant doit avoir UNE seule responsabilité.

```javascript
// [X] MAUVAIS : Fait trop de choses
function UserDashboard() {
  return (
    <div>
      {/* Profil utilisateur */}
      <div className="profile">
        <img src="..." />
        <h1>...</h1>
        <p>...</p>
      </div>
      
      {/* Liste d'amis */}
      <div className="friends">
        <h2>Amis</h2>
        <ul>...</ul>
      </div>
      
      {/* Posts */}
      <div className="posts">
        <h2>Posts</h2>
        {/* 100 lignes de code... */}
      </div>
    </div>
  );
}

// [OK] BON : Décomposé en petits composants
function UserDashboard() {
  return (
    <div>
      <UserProfile />
      <FriendsList />
      <PostsList />
    </div>
  );
}
```


## 3. Nommer les Props Clairement

```javascript
// [X] MAUVAIS : Noms vagues
function Button({ a, b, c }) {
  return <button onClick={a}>{b}</button>;
}

// [OK] BON : Noms descriptifs
function Button({ onClick, text, variant }) {
  return <button onClick={onClick}>{text}</button>;
}
```


## 4. Éviter les Props Booléens Ambigus

```javascript
// [X] CONFUS
<Button flag={true} />  // flag de quoi ?

// [OK] CLAIR
<Button disabled={true} />
<Button isLoading={true} />
<Button hasError={true} />
```


## 5. Utiliser des Valeurs par Défaut

```javascript
function Button({ 
  text = 'Cliquer',
  variant = 'primary',
  disabled = false,
  size = 'medium'
}) {
  return (
    <button 
      className={`btn btn-${variant} btn-${size}`}
      disabled={disabled}
    >
      {text}
    </button>
  );
}

// Utilisation simple
<Button />  // Utilise toutes les valeurs par défaut
<Button text="Valider" />  // Personnalise seulement text
```


## 6. Composants Petits et Réutilisables

```javascript
// [OK] PRINCIPE : Si vous copiez-collez du code, créez un composant !

// Avant (répétition)
<div className="card">
  <h2>Titre 1</h2>
  <p>Description 1</p>
</div>

<div className="card">
  <h2>Titre 2</h2>
  <p>Description 2</p>
</div>

// Après (composant réutilisable)
function Card({ title, description }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <p>{description}</p>
    </div>
  );
}

<Card title="Titre 1" description="Description 1" />
<Card title="Titre 2" description="Description 2" />
```


## 7. Extraire la Logique Complexe

```javascript
// [X] MAUVAIS : Logique mélangée avec JSX
function UserList() {
  return (
    <ul>
      {users
        .filter(u => u.isActive)
        .filter(u => u.age >= 18)
        .filter(u => u.verified)
        .sort((a, b) => a.name.localeCompare(b.name))
        .map(u => <li key={u.id}>{u.name}</li>)
      }
    </ul>
  );
}

// [OK] BON : Logique extraite
function UserList() {
  const activeAdultVerifiedUsers = users
    .filter(u => u.isActive && u.age >= 18 && u.verified)
    .sort((a, b) => a.name.localeCompare(b.name));
  
  return (
    <ul>
      {activeAdultVerifiedUsers.map(u => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

// [OK] ENCORE MIEUX : Fonction utilitaire
function getActiveAdultUsers(users) {
  return users
    .filter(u => u.isActive && u.age >= 18 && u.verified)
    .sort((a, b) => a.name.localeCompare(b.name));
}

function UserList() {
  const filteredUsers = getActiveAdultUsers(users);
  
  return (
    <ul>
      {filteredUsers.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}
```


## 8. Commentaires Utiles

```javascript
function ComplexComponent() {
  return (
    <div>
      {/* Section profil utilisateur */}
      <UserProfile />
      
      {/* Liste des posts avec pagination */}
      <PostsList />
      
      {/* TODO: Ajouter système de notifications */}
      
      {/* HACK: Fix temporaire pour bug #123 */}
      {isOldBrowser && <LegacySupport />}
    </div>
  );
}
```



# === PATTERNS AVANCÉS ===

## Pattern 1 : Render Props

```javascript
// Composant qui gère la logique, laisse le rendu au parent
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const handleMouseMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    
    window.addEventListener('mousemove', handleMouseMove);
    return () => window.removeEventListener('mousemove', handleMouseMove);
  }, []);
  
  return render(position);  // Appelle la fonction render
}

// Utilisation
<MouseTracker 
  render={(pos) => (
    <div>
      <h1>Position : {pos.x}, {pos.y}</h1>
    </div>
  )}
/>
```


## Pattern 2 : Compound Components

```javascript
// Composants qui travaillent ensemble
function Tabs({ children, defaultTab }) {
  const [activeTab, setActiveTab] = useState(defaultTab);
  
  return (
    <div className="tabs">
      {React.Children.map(children, child => {
        return React.cloneElement(child, { activeTab, setActiveTab });
      })}
    </div>
  );
}

function TabList({ children, activeTab, setActiveTab }) {
  return (
    <div className="tab-list">
      {React.Children.map(children, (child, index) => {
        return React.cloneElement(child, { 
          isActive: activeTab === index,
          onClick: () => setActiveTab(index)
        });
      })}
    </div>
  );
}

function Tab({ children, isActive, onClick }) {
  return (
    <button 
      className={isActive ? 'tab active' : 'tab'}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

function TabPanel({ children, activeTab, index }) {
  if (activeTab !== index) return null;
  return <div className="tab-panel">{children}</div>;
}

// Utilisation (API élégante)
<Tabs defaultTab={0}>
  <TabList>
    <Tab>Tab 1</Tab>
    <Tab>Tab 2</Tab>
    <Tab>Tab 3</Tab>
  </TabList>
  
  <TabPanel index={0}>Contenu 1</TabPanel>
  <TabPanel index={1}>Contenu 2</TabPanel>
  <TabPanel index={2}>Contenu 3</TabPanel>
</Tabs>
```


## Pattern 3 : Higher-Order Component (HOC)

```javascript
// Fonction qui prend un composant et retourne un nouveau composant
function withLoading(Component) {
  return function WithLoadingComponent({ isLoading, ...props }) {
    if (isLoading) {
      return <div>Chargement...</div>;
    }
    return <Component {...props} />;
  };
}

// Composant de base
function UserList({ users }) {
  return (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

// Composant amélioré avec loading
const UserListWithLoading = withLoading(UserList);

// Utilisation
<UserListWithLoading 
  isLoading={loading} 
  users={users} 
/>
```



# === EXERCICES PRATIQUES ===

## Exercice 1 : Carte de Produit

Créez un composant ProductCard avec :
- Image du produit
- Nom du produit
- Prix
- Bouton "Ajouter au panier"
- Badge "Nouveau" si le produit a moins de 7 jours

```javascript
// Structure attendue
<ProductCard
  name="iPhone 14"
  price={999}
  image="/images/iphone.jpg"
  addedDate="2024-01-15"
  onAddToCart={() => console.log('Ajouté')}
/>
```

SOLUTION :
```javascript
function ProductCard({ name, price, image, addedDate, onAddToCart }) {
  // Calculer si nouveau (moins de 7 jours)
  const isNew = () => {
    const productDate = new Date(addedDate);
    const now = new Date();
    const diffDays = (now - productDate) / (1000 * 60 * 60 * 24);
    return diffDays <= 7;
  };
  
  return (
    <div className="product-card">
      <div className="product-image">
        <img src={image} alt={name} />
        {isNew() && <span className="badge-new">Nouveau</span>}
      </div>
      
      <div className="product-info">
        <h3>{name}</h3>
        <p className="price">{price} €</p>
      </div>
      
      <button onClick={onAddToCart} className="btn-add">
        Ajouter au panier
      </button>
    </div>
  );
}
```


## Exercice 2 : Système d'Onglets

Créez un composant Tabs réutilisable.

SOLUTION :
```javascript
function Tabs({ tabs }) {
  const [activeTab, setActiveTab] = useState(0);
  
  return (
    <div className="tabs-container">
      {/* Boutons des onglets */}
      <div className="tabs-buttons">
        {tabs.map((tab, index) => (
          <button
            key={index}
            className={activeTab === index ? 'tab-btn active' : 'tab-btn'}
            onClick={() => setActiveTab(index)}
          >
            {tab.label}
          </button>
        ))}
      </div>
      
      {/* Contenu de l'onglet actif */}
      <div className="tab-content">
        {tabs[activeTab].content}
      </div>
    </div>
  );
}

// Utilisation
const myTabs = [
  { label: 'Profil', content: <UserProfile /> },
  { label: 'Paramètres', content: <Settings /> },
  { label: 'Notifications', content: <Notifications /> }
];

<Tabs tabs={myTabs} />
```


## Exercice 3 : Formulaire de Contact

Créez un formulaire avec validation.

SOLUTION :
```javascript
function ContactForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: ''
  });
  
  const [errors, setErrors] = useState({});
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
    
    // Effacer l'erreur quand l'utilisateur tape
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  };
  
  const validate = () => {
    const newErrors = {};
    
    if (!formData.name.trim()) {
      newErrors.name = 'Le nom est requis';
    }
    
    if (!formData.email.trim()) {
      newErrors.email = 'L\'email est requis';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'Email invalide';
    }
    
    if (!formData.message.trim()) {
      newErrors.message = 'Le message est requis';
    } else if (formData.message.length < 10) {
      newErrors.message = 'Le message doit faire au moins 10 caractères';
    }
    
    return newErrors;
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    const newErrors = validate();
    
    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors);
      return;
    }
    
    // Soumettre le formulaire
    console.log('Formulaire valide:', formData);
    alert('Message envoyé !');
    
    // Réinitialiser
    setFormData({ name: '', email: '', message: '' });
  };
  
  return (
    <form onSubmit={handleSubmit} className="contact-form">
      <div className="form-group">
        <label htmlFor="name">Nom :</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
          className={errors.name ? 'error' : ''}
        />
        {errors.name && <span className="error-message">{errors.name}</span>}
      </div>
      
      <div className="form-group">
        <label htmlFor="email">Email :</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          className={errors.email ? 'error' : ''}
        />
        {errors.email && <span className="error-message">{errors.email}</span>}
      </div>
      
      <div className="form-group">
        <label htmlFor="message">Message :</label>
        <textarea
          id="message"
          name="message"
          value={formData.message}
          onChange={handleChange}
          rows="5"
          className={errors.message ? 'error' : ''}
        />
        {errors.message && <span className="error-message">{errors.message}</span>}
      </div>
      
      <button type="submit" className="btn-submit">
        Envoyer
      </button>
    </form>
  );
}
```



# === RÉSUMÉ DES CONCEPTS ===

```
┌──────────────────────────────────────────────────────────────────┐
│  CONCEPT              │  DESCRIPTION                             │
├───────────────────────┼──────────────────────────────────────────┤
│  COMPOSANT            │  Fonction qui retourne du JSX            │
│  FONCTIONNEL          │  Approche moderne avec Hooks             │
├───────────────────────┼──────────────────────────────────────────┤
│  PROPS                │  Paramètres passés au composant          │
│                       │  Lecture seule, parent -> enfant          │
├───────────────────────┼──────────────────────────────────────────┤
│  CHILDREN             │  Prop spéciale pour contenu entre       │
│                       │  balises <Component>...</Component>      │
├───────────────────────┼──────────────────────────────────────────┤
│  DESTRUCTURING        │  Extraire props directement :            │
│                       │  ({ name, age }) => ...                  │
├───────────────────────┼──────────────────────────────────────────┤
│  PROPTYPES            │  Validation des types de props          │
│                       │  Warnings si types incorrects            │
├───────────────────────┼──────────────────────────────────────────┤
│  COMPOSITION          │  Assembler petits composants pour       │
│                       │  créer des plus grands                   │
├───────────────────────┼──────────────────────────────────────────┤
│  COMPOSANT PURE       │  Même props -> Même résultat              │
│                       │  Pas d'effets secondaires                │
└──────────────────────────────────────────────────────────────────┘
```



# === CHECKLIST AVANT DE CRÉER UN COMPOSANT ===

Posez-vous ces questions :

[WHITE_SQUARE] Le composant a-t-il UNE seule responsabilité ?
[WHITE_SQUARE] Le nom du composant est-il clair et descriptif ?
[WHITE_SQUARE] Le composant commence-t-il par une MAJUSCULE ?
[WHITE_SQUARE] Les props sont-elles bien nommées ?
[WHITE_SQUARE] Y a-t-il des valeurs par défaut pour les props optionnelles ?
[WHITE_SQUARE] Le composant est-il réutilisable ?
[WHITE_SQUARE] Le code est-il facile à comprendre ?
[WHITE_SQUARE] Ai-je extrait la logique complexe hors du JSX ?
[WHITE_SQUARE] Les props sont-elles validées (PropTypes ou TypeScript) ?
[WHITE_SQUARE] Le composant est-il pur (pas d'effets secondaires) ?



# === RESSOURCES POUR APPROFONDIR ===

## Documentation officielle
https://react.dev/learn/your-first-component
https://react.dev/learn/passing-props-to-a-component

## Tutoriels recommandés
- React Beta Docs : https://react.dev/learn
- MDN Web Docs : https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started

## Outils
- React DevTools : Inspecter composants dans le navigateur
- ESLint plugin : Détecter erreurs courantes



# === PROCHAINES ÉTAPES ===

Maintenant que vous maîtrisez les composants, continuez avec :

1. [OK] COMPOSANTS (ce fichier)
   └─ Création, props, composition

2. -> JSX en profondeur
   └─ Syntaxe avancée, expressions, conditionnels

3. -> useState (gestion d'état)
   └─ Données qui changent, re-renders

4. -> useEffect (effets de bord)
   └─ API calls, timers, subscriptions

5. -> Formulaires
   └─ Inputs contrôlés, validation

6. -> Listes et clés
   └─ map(), key, performance



# === CONCLUSION ===

Félicitations ! Vous savez maintenant :
[OK] Créer des composants fonctionnels
[OK] Utiliser les props pour passer des données
[OK] Déstructurer les props pour un code plus propre
[OK] Utiliser children pour des composants conteneurs
[OK] Valider les props avec PropTypes
[OK] Composer des composants pour créer des interfaces complexes
[OK] Suivre les bonnes pratiques React

Les composants sont la FONDATION de React.
Maîtrisez-les, et le reste deviendra beaucoup plus facile !



[OK] QU'EST-CE QUE LE JSX ? - EXPLICATION APPROFONDIE


JSX = JavaScript XML
C'est une EXTENSION de syntaxe JavaScript qui permet d'écrire du code
qui RESSEMBLE à du HTML, mais c'est en fait du JavaScript !


# === ANALOGIE : JSX = SUCRE SYNTAXIQUE ===

Imaginez que vous voulez dire "Je t'aime" :

VERSION FORMELLE (JavaScript pur) :
"Je te témoigne mon affection la plus sincère et profonde"
-> React.createElement('div', null, 'Je t\'aime')

VERSION SIMPLE (JSX) :
"Je t'aime"
-> <div>Je t'aime</div>

Les deux disent la MÊME CHOSE, mais le JSX est beaucoup plus facile à lire !


# === POURQUOI JSX EXISTE-T-IL ? ===

## PROBLÈME : Code React sans JSX est illisible

```javascript
// SANS JSX - JavaScript pur (HORRIBLE à lire)
const element = React.createElement(
  'div',
  { className: 'container', id: 'main' },
  React.createElement(
    'h1',
    { style: { color: 'blue' } },
    'Bienvenue'
  ),
  React.createElement(
    'p',
    null,
    'Ceci est un paragraphe'
  )
);
```

PROBLÈMES :
[X] Difficile de visualiser la structure
[X] Beaucoup de répétition (React.createElement)
[X] Imbrication confuse
[X] Difficile à maintenir


## SOLUTION : JSX rend le code lisible

```javascript
// AVEC JSX - Facile à lire (MAGNIFIQUE)
const element = (
  <div className="container" id="main">
    <h1 style={{ color: 'blue' }}>Bienvenue</h1>
    <p>Ceci est un paragraphe</p>
  </div>
);
```

AVANTAGES :
[OK] Structure visuelle claire
[OK] Ressemble à du HTML (familier)
[OK] Facile à écrire et maintenir
[OK] Moins de code


# === COMMENT LE JSX FONCTIONNE-T-IL ? ===

## Le processus de compilation

```
┌─────────────────────────────────────────────────────────────┐
│  1. VOUS ÉCRIVEZ du JSX                                     │
│     <h1>Hello</h1>                                          │
│                                                             │
│  2. BABEL (compilateur) transforme en JavaScript           │
│     React.createElement('h1', null, 'Hello')                │
│                                                             │
│  3. REACT crée un objet JavaScript                          │
│     { type: 'h1', props: { children: 'Hello' } }           │
│                                                             │
│  4. REACT génère le HTML dans le DOM                        │
│     <h1>Hello</h1>                                          │
└─────────────────────────────────────────────────────────────┘
```

IMPORTANT : Le navigateur ne comprend PAS le JSX !
Babel le transforme en JavaScript avant d'être exécuté.


## Exemple de transformation détaillée

```javascript
// ═══════════════════════════════════════════════════════════
// ÉTAPE 1 : VOTRE CODE (JSX)
// ═══════════════════════════════════════════════════════════
const element = <h1 className="title">Bonjour {name}</h1>;


// ═══════════════════════════════════════════════════════════
// ÉTAPE 2 : BABEL TRANSFORME (JavaScript pur)
// ═══════════════════════════════════════════════════════════
const element = React.createElement(
  'h1',                    // Type d'élément
  { className: 'title' },  // Props (attributs)
  'Bonjour ',              // Enfant 1 (texte)
  name                     // Enfant 2 (variable)
);


// ═══════════════════════════════════════════════════════════
// ÉTAPE 3 : REACT CRÉE L'OBJET
// ═══════════════════════════════════════════════════════════
{
  type: 'h1',
  props: {
    className: 'title',
    children: ['Bonjour ', name]
  }
}


// ═══════════════════════════════════════════════════════════
// ÉTAPE 4 : RÉSULTAT DANS LE DOM
// ═══════════════════════════════════════════════════════════
<h1 class="title">Bonjour Alice</h1>
```


# === JSX N'EST PAS DU HTML ! ===

## Différences importantes

```javascript
// ╔═══════════════════════════════════════════════════════════╗
// ║  HTML vs JSX - TABLEAU COMPARATIF                         ║
// ╚═══════════════════════════════════════════════════════════╝

// ┌─────────────────────────────────────────────────────────┐
// │  ATTRIBUTS                                              │
// └─────────────────────────────────────────────────────────┘

// HTML
<div class="container"></div>
<label for="email"></label>

// JSX (utilise noms JavaScript)
<div className="container"></div>
<label htmlFor="email"></label>


// ┌─────────────────────────────────────────────────────────┐
// │  FERMETURE DES BALISES                                  │
// └─────────────────────────────────────────────────────────┘

// HTML (peut ne pas être fermé)
<input type="text">
<img src="photo.jpg">
<br>

// JSX (DOIT être fermé)
<input type="text" />
<img src="photo.jpg" />
<br />


// ┌─────────────────────────────────────────────────────────┐
// │  STYLES                                                 │
// └─────────────────────────────────────────────────────────┘

// HTML (string CSS)
<div style="color: red; font-size: 20px;"></div>

// JSX (objet JavaScript)
<div style={{ color: 'red', fontSize: '20px' }}></div>


// ┌─────────────────────────────────────────────────────────┐
// │  COMMENTAIRES                                           │
// └─────────────────────────────────────────────────────────┘

// HTML
<!-- Ceci est un commentaire -->

// JSX
{/* Ceci est un commentaire */}


// ┌─────────────────────────────────────────────────────────┐
// │  CASSE (majuscules/minuscules)                          │
// └─────────────────────────────────────────────────────────┘

// HTML (insensible à la casse)
<DIV CLASS="test"></DIV>

// JSX (sensible à la casse)
<div className="test"></div>  // [OK] Correct
<Div ClassName="test"></Div>  // [X] Erreur
```


# === RÈGLE #1 : EXPRESSIONS JAVASCRIPT AVEC {} ===

Les accolades {} permettent d'insérer du JavaScript dans le JSX


## Concept : {} = Fenêtre vers JavaScript

```javascript
// Pensez au JSX comme du HTML avec des "fenêtres" {}
// Par ces fenêtres, vous pouvez voir et utiliser JavaScript

const name = 'Alice';
const age = 25;

const element = (
  <div>
    {/* Fenêtre 1 : afficher une variable */}
    <h1>Bonjour {name}</h1>
    
    {/* Fenêtre 2 : faire un calcul */}
    <p>Dans 5 ans, tu auras {age + 5} ans</p>
    
    {/* Fenêtre 3 : appeler une fonction */}
    <p>Nombre aléatoire : {Math.random()}</p>
  </div>
);
```


## Que peut-on mettre dans {} ?

### [OK] EXPRESSIONS (retournent une valeur)

```javascript
// Variables
{name}
{user.email}
{items[0]}

// Opérations mathématiques
{2 + 2}
{price * 1.2}
{count++}

// Opérations sur strings
{firstName + ' ' + lastName}
{`Bonjour ${name}`}  // Template literals

// Booléens (ne s'affichent pas, mais peuvent être utilisés)
{true}   // N'affiche rien
{false}  // N'affiche rien

// Opérateur ternaire (if-else court)
{age >= 18 ? 'Adulte' : 'Mineur'}
{isLoggedIn ? <Profile /> : <Login />}

// Opérateur && (AND logique)
{isVisible && <Component />}

// Opérateur || (OR logique)
{username || 'Invité'}

// Appels de fonction
{getUserName()}
{formatDate(date)}
{calculate(a, b)}

// Méthodes de tableau
{items.map(item => <li>{item}</li>)}
{items.filter(item => item.active)}
{items.length}

// Accès aux propriétés
{user.name}
{user.address.city}
{settings['theme']}

// Optional chaining (évite les erreurs)
{user?.name}
{user?.address?.city}

// Nullish coalescing (valeur par défaut)
{username ?? 'Anonyme'}
```


### [X] STATEMENTS (ne retournent PAS de valeur)

```javascript
// [X] if/else classique
{if (x > 5) {
  return <div>Grand</div>
}}

// [X] Boucle for
{for (let i = 0; i < 10; i++) {
  <div>{i}</div>
}}

// [X] Switch
{switch (type) {
  case 'a': return <A />;
}}

// [X] Déclaration de variable
{const x = 5}

// [X] Déclaration de fonction
{function test() {}}
```


## SOLUTIONS pour les statements

### Solution 1 : Utiliser le ternaire (au lieu de if-else)

```javascript
// [X] INTERDIT
{if (age >= 18) {
  <p>Adulte</p>
} else {
  <p>Mineur</p>
}}

// [OK] CORRECT - Ternaire
{age >= 18 ? <p>Adulte</p> : <p>Mineur</p>}

// [OK] CORRECT - Variable avant le return
function Component({ age }) {
  let status;
  if (age >= 18) {
    status = <p>Adulte</p>;
  } else {
    status = <p>Mineur</p>;
  }
  
  return <div>{status}</div>;
}
```


### Solution 2 : Utiliser map (au lieu de for)

```javascript
// [X] INTERDIT
{for (let i = 0; i < items.length; i++) {
  <li>{items[i]}</li>
}}

// [OK] CORRECT - Map
{items.map((item, index) => (
  <li key={index}>{item}</li>
))}

// [OK] CORRECT - Avec indices
{Array.from({ length: 5 }, (_, i) => (
  <div key={i}>Élément {i}</div>
))}
```


### Solution 3 : Utiliser && (au lieu de if simple)

```javascript
// [X] INTERDIT
{if (isLoggedIn) {
  <Welcome />
}}

// [OK] CORRECT - Opérateur &&
{isLoggedIn && <Welcome />}

// EXPLICATION du &&
// false && <Component />  -> rien ne s'affiche
// true && <Component />   -> <Component /> s'affiche
```


### Solution 4 : Fonction ou variable pour logique complexe

```javascript
function UserStatus({ user }) {
  // Toute la logique AVANT le return
  let statusElement;
  
  if (user.isPremium) {
    statusElement = <PremiumBadge />;
  } else if (user.isVerified) {
    statusElement = <VerifiedBadge />;
  } else {
    statusElement = <NormalBadge />;
  }
  
  // JSX simple
  return (
    <div>
      <h2>{user.name}</h2>
      {statusElement}
    </div>
  );
}
```


## Exemples pratiques détaillés

### Exemple 1 : Afficher des informations utilisateur

```javascript
const user = {
  name: 'Alice Dupont',
  age: 28,
  email: 'alice@example.com',
  isPremium: true
};

function UserCard() {
  return (
    <div className="user-card">
      {/* Variable simple */}
      <h2>{user.name}</h2>
      
      {/* Propriété d'objet */}
      <p>Email : {user.email}</p>
      
      {/* Calcul */}
      <p>Née en : {2024 - user.age}</p>
      
      {/* Ternaire */}
      <p>Statut : {user.isPremium ? '* Premium' : 'Standard'}</p>
      
      {/* Opérateur && */}
      {user.isPremium && (
        <div className="premium-badge">
          Membre Premium
        </div>
      )}
    </div>
  );
}
```


### Exemple 2 : Afficher une liste

```javascript
const fruits = ['Pomme', 'Banane', 'Orange', 'Fraise'];

function FruitList() {
  return (
    <div>
      <h2>Ma liste de fruits ({fruits.length} fruits)</h2>
      
      <ul>
        {fruits.map((fruit, index) => (
          <li key={index}>
            {/* Index + 1 pour commencer à 1 au lieu de 0 */}
            {index + 1}. {fruit}
          </li>
        ))}
      </ul>
      
      {/* Afficher message si liste vide */}
      {fruits.length === 0 && (
        <p>Aucun fruit dans la liste</p>
      )}
    </div>
  );
}

// Résultat affiché :
// Ma liste de fruits (4 fruits)
// • 1. Pomme
// • 2. Banane
// • 3. Orange
// • 4. Fraise
```


### Exemple 3 : Logique complexe

```javascript
function WeatherWidget({ temperature, condition }) {
  return (
    <div className="weather">
      <h3>Météo</h3>
      
      {/* Ternaires imbriqués (attention, peut devenir illisible) */}
      <p className={
        temperature > 30 ? 'hot' :
        temperature > 20 ? 'warm' :
        temperature > 10 ? 'cool' :
        'cold'
      }>
        {temperature}°C
      </p>
      
      {/* Affichage conditionnel multiple */}
      {condition === 'sunny' && <span>[BLACK_SUN_WITH_RAYS] Ensoleillé</span>}
      {condition === 'rainy' && <span>[CLOUD_WITH_RAIN] Pluvieux</span>}
      {condition === 'cloudy' && <span>[CLOUD] Nuageux</span>}
      
      {/* Conseil basé sur température */}
      <p>
        Conseil : {
          temperature > 30 ? 'Restez hydraté!' :
          temperature < 0 ? 'Habillez-vous chaudement!' :
          'Profitez de la journée!'
        }
      </p>
    </div>
  );
}
```


# === RÈGLE #2 : ATTRIBUTS EN camelCase ===

JSX utilise la convention JavaScript (camelCase) pour les attributs


## Tableau de conversion HTML -> JSX

```javascript
// ╔═══════════════════════════════════════════════════════════╗
// ║  ATTRIBUTS HTML vs JSX                                    ║
// ╚═══════════════════════════════════════════════════════════╝

HTML                      JSX                        Raison
─────────────────────────────────────────────────────────────
class="..."          ->   className="..."        'class' = mot-clé JS
for="..."            ->   htmlFor="..."          'for' = mot-clé JS
tabindex="1"         ->   tabIndex={1}           camelCase
readonly             ->   readOnly               camelCase
maxlength="10"       ->   maxLength={10}         camelCase
cellspacing="0"      ->   cellSpacing={0}        camelCase
rowspan="2"          ->   rowSpan={2}            camelCase
colspan="3"          ->   colSpan={3}            camelCase
onclick="..."        ->   onClick={...}          camelCase
onchange="..."       ->   onChange={...}         camelCase
onfocus="..."        ->   onFocus={...}          camelCase
onblur="..."         ->   onBlur={...}           camelCase
```


## Pourquoi ces changements ?

### class -> className

```javascript
// PROBLÈME en JavaScript :
class MyClass {  // 'class' est un mot-clé réservé !
  // ...
}

// SOLUTION en JSX :
<div className="container">  // Pas de conflit
```


### for -> htmlFor

```javascript
// PROBLÈME en JavaScript :
for (let i = 0; i < 10; i++) {  // 'for' est un mot-clé réservé !
  // ...
}

// SOLUTION en JSX :
<label htmlFor="email">Email</label>  // Pas de conflit
```


## Exemples d'attributs

```javascript
function FormExample() {
  return (
    <form className="my-form" id="contact-form">
      {/* Label avec htmlFor */}
      <label htmlFor="email" className="form-label">
        Email
      </label>
      
      {/* Input avec plusieurs attributs */}
      <input
        id="email"
        type="email"
        className="form-input"
        placeholder="votre@email.com"
        maxLength={50}
        required
        autoComplete="email"
        tabIndex={1}
      />
      
      {/* Textarea avec readOnly */}
      <textarea
        className="form-textarea"
        rows={5}
        cols={40}
        readOnly
        defaultValue="Texte en lecture seule"
      />
      
      {/* Bouton avec tabIndex */}
      <button
        type="submit"
        className="form-button"
        tabIndex={2}
      >
        Envoyer
      </button>
    </form>
  );
}
```


# === RÈGLE #3 : STYLES EN OBJET JAVASCRIPT ===

En JSX, les styles inline sont des objets JavaScript, pas des strings CSS


## Syntaxe des styles

```javascript
// ╔═══════════════════════════════════════════════════════════╗
// ║  STYLES : HTML vs JSX                                     ║
// ╚═══════════════════════════════════════════════════════════╝

// ═══════════════════════════════════════════════════════════
// HTML (string CSS)
// ═══════════════════════════════════════════════════════════
<div style="color: red; font-size: 20px; background-color: blue;">
  Texte
</div>


// ═══════════════════════════════════════════════════════════
// JSX (objet JavaScript)
// ═══════════════════════════════════════════════════════════
<div style={{ color: 'red', fontSize: '20px', backgroundColor: 'blue' }}>
  Texte
</div>
//           ^^ Deux accolades !
//           ^  Première paire = "J'insère du JavaScript"
//            ^ Deuxième paire = "C'est un objet"
```


## Conversion CSS -> JSX

```javascript
// ╔═══════════════════════════════════════════════════════════╗
// ║  PROPRIÉTÉS CSS vs JSX                                    ║
// ╚═══════════════════════════════════════════════════════════╝

CSS (kebab-case)          JSX (camelCase)           Valeur
─────────────────────────────────────────────────────────────
color: red;          ->    color: 'red'
font-size: 20px;     ->    fontSize: '20px'
background-color     ->    backgroundColor: '#fff'
margin-top: 10px;    ->    marginTop: '10px'
padding-left: 5px;   ->    paddingLeft: '5px'
border-radius: 5px;  ->    borderRadius: '5px'
text-align: center;  ->    textAlign: 'center'
z-index: 10;         ->    zIndex: 10
```


## Exemples de styles

### Style inline simple

```javascript
function StyledComponent() {
  return (
    <div style={{ color: 'blue', fontSize: '20px' }}>
      Texte bleu en 20px
    </div>
  );
}
```


### Style dans une variable

```javascript
function BoxComponent() {
  // Définir le style dans une variable (plus lisible)
  const boxStyle = {
    width: '200px',
    height: '200px',
    backgroundColor: '#3498db',
    color: 'white',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: '10px',
    boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
  };
  
  return (
    <div style={boxStyle}>
      Je suis une boîte stylée
    </div>
  );
}
```


### Style dynamique

```javascript
function DynamicButton({ isPrimary, isLarge }) {
  // Style change selon les props
  const buttonStyle = {
    padding: isLarge ? '15px 30px' : '10px 20px',
    fontSize: isLarge ? '18px' : '14px',
    backgroundColor: isPrimary ? '#007bff' : '#6c757d',
    color: 'white',
    border: 'none',
    borderRadius: '5px',
    cursor: 'pointer'
  };
  
  return (
    <button style={buttonStyle}>
      Cliquer
    </button>
  );
}

// Utilisation
<DynamicButton isPrimary={true} isLarge={false} />
```


### Combiner styles

```javascript
function Component() {
  const baseStyle = {
    padding: '10px',
    margin: '5px'
  };
  
  const colorStyle = {
    backgroundColor: 'blue',
    color: 'white'
  };
  
  // Spread operator pour combiner
  const combinedStyle = {
    ...baseStyle,
    ...colorStyle,
    fontSize: '16px'  // Ajouter une nouvelle propriété
  };
  
  return <div style={combinedStyle}>Texte</div>;
}
```


### Unités de mesure

```javascript
function UnitsExample() {
  const styles = {
    // Avec unité (string)
    width: '100px',
    height: '50%',
    margin: '1em',
    padding: '2rem',
    
    // Sans unité (number) - React ajoute 'px' automatiquement
    fontSize: 16,      // -> '16px'
    marginTop: 20,     // -> '20px'
    zIndex: 10,        // -> '10' (pas de px)
    opacity: 0.5,      // -> '0.5' (pas de px)
    
    // Calculs
    width: `${100 / 3}%`,  // -> '33.333333%'
    height: `calc(100vh - 60px)`
  };
  
  return <div style={styles}>Contenu</div>;
}
```


## IMPORTANT : Préférer les classes CSS

```javascript
// [X] ÉVITER : Trop de styles inline
<div style={{ 
  width: '300px',
  height: '200px',
  backgroundColor: '#fff',
  // ... 20 lignes de styles ...
}}>

// [OK] PRÉFÉRER : Classes CSS
<div className="my-component">

// Dans le fichier CSS
.my-component {
  width: 300px;
  height: 200px;
  background-color: #fff;
  /* ... */
}
```

RAISON : Styles inline = moins performants et moins maintenables


# === RÈGLE #4 : FERMER TOUTES LES BALISES ===

En JSX, TOUTES les balises doivent être fermées (même les self-closing)


## Balises auto-fermantes

```javascript
// ╔═══════════════════════════════════════════════════════════╗
// ║  BALISES AUTO-FERMANTES                                   ║
// ╚═══════════════════════════════════════════════════════════╝

// ═══════════════════════════════════════════════════════════
// HTML (peut ne pas être fermé)
// ═══════════════════════════════════════════════════════════
<input type="text">
<img src="photo.jpg">
<br>
<hr>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">


// ═══════════════════════════════════════════════════════════
// JSX (DOIT être fermé avec />)
// ═══════════════════════════════════════════════════════════
<input type="text" />
<img src="photo.jpg" />
<br />
<hr />
<meta charset="utf-8" />
<link rel="stylesheet" href="style.css" />
```


## Pourquoi cette règle ?

JSX doit être du XML valide, et XML exige que toutes les balises soient fermées.

```javascript
// [X] ERREUR en JSX
<input type="text">      // Syntax error!
<img src="photo.jpg">    // Syntax error!

// [OK] CORRECT en JSX
<input type="text" />
<img src="photo.jpg" />
```


## Balises avec contenu

```javascript
// Les balises avec contenu se ferment normalement
<div>Contenu</div>
<p>Paragraphe</p>
<button>Cliquer</button>

// Mais peuvent aussi s'auto-fermer si vides (rare)
<div></div>     // [OK] Valide
<div />         // [OK] Valide aussi (mais peu utilisé)
```


# === RÈGLE #5 : UN SEUL ÉLÉMENT RACINE ===

Un composant ne peut retourner qu'UN SEUL élément racine


## Le problème

```javascript
// [X] ERREUR : Deux éléments racines
function Component() {
  return (
    <h1>Titre</h1>
    <p>Paragraphe</p>
  );
}
// Erreur : Adjacent JSX elements must be wrapped in an enclosing tag
```

POURQUOI ? Une fonction JavaScript ne peut retourner qu'UNE SEULE valeur !

```javascript
// Même problème en JavaScript pur
function test() {
  return
    "valeur 1"    // [X] Erreur de syntaxe
    "valeur 2"
}
```


## Solution 1 : Wrapper <div>

```javascript
// [OK] CORRECT : Envelopper dans un <div>
function Component() {
  return (
    <div>
      <h1>Titre</h1>
      <p>Paragraphe</p>
    </div>
  );
}
```

INCONVÉNIENT : Ajoute un <div> inutile dans le DOM

```html
<!-- Résultat dans le DOM -->
<div>      <!-- <- Ce div n'est là que pour React -->
  <h1>Titre</h1>
  <p>Paragraphe</p>
</div>
```


## Solution 2 : Fragment <> (RECOMMANDÉ)

```javascript
// [OK] MEILLEUR : Fragment (pas de div dans le DOM)
function Component() {
  return (
    <>
      <h1>Titre</h1>
      <p>Paragraphe</p>
    </>
  );
}
```

AVANTAGE : Pas de balise supplémentaire dans le DOM !

```html
<!-- Résultat dans le DOM -->
<h1>Titre</h1>      <!-- <- Pas de wrapper -->
<p>Paragraphe</p>
```


## Fragment explicite (avec key)

```javascript
import { Fragment } from 'react';

function List({ items }) {
  return (
    <>
      {items.map(item => (
        // Fragment explicite nécessaire si on a besoin d'une key
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </>
  );
}
```


## Quand utiliser quoi ?

```javascript
// [OK] Fragment <> : La plupart du temps
<>
  <Header />
  <Main />
  <Footer />
</>

// [OK] Div : Si vous avez besoin de styles ou events
<div className="container" onClick={handleClick}>
  <Header />
  <Main />
</div>

// [OK] Fragment explicite : Dans les listes avec key
<Fragment key={id}>
  <Element1 />
  <Element2 />
</Fragment>
```


# === RÈGLE #6 : COMMENTAIRES EN JSX ===

Les commentaires JSX ont une syntaxe spéciale


## Syntaxe des commentaires

```javascript
function Component() {
  return (
    <div>
      {/* Ceci est un commentaire JSX */}
      
      {/* 
        Commentaire
        sur plusieurs
        lignes
      */}
      
      <h1>Titre</h1>
      
      {/* Commentaire avant un élément */}
      <p>Paragraphe</p>
    </div>
  );
}
```


## Pourquoi cette syntaxe ?

```javascript
// Dans le JSX, tout ce qui est entre {} est du JavaScript
// Donc on utilise {/* */} pour les commentaires JS

{/* Commentaire JSX */}
// ^   ^              ^
// │   │              │
// │   │              └─ Fermeture commentaire JS
// │   └──────────────── Commentaire JavaScript
// └──────────────────── Accolades JSX
```


## Erreurs courantes

```javascript
function Component() {
  return (
    <div>
      <!-- Ceci NE FONCTIONNE PAS -->
      {/* [OK] Ceci fonctionne */}
      
      // Ceci ne fonctionne pas non plus
      {/* [OK] Ceci fonctionne */}
      
      /* Ceci non plus */
      {/* [OK] Ceci fonctionne */}
    </div>
  );
}
```


## Commentaires hors JSX

```javascript
function Component() {
  // Commentaire JavaScript normal (en dehors du JSX)
  const value = 42;
  
  /* Commentaire bloc
     en dehors du JSX */
  
  return (
    <div>
      {/* Commentaire JSX (dans le JSX) */}
      <p>Contenu</p>
    </div>
  );
}
```


# === LISTES ET CLÉS (KEYS) ===

Afficher des listes est l'une des opérations les plus courantes en React


## Le problème des listes

```javascript
const fruits = ['Pomme', 'Banane', 'Orange'];

function FruitList() {
  return (
    <ul>
      {fruits.map(fruit => (
        <li>{fruit}</li>
      ))}
    </ul>
  );
}
```

PROBLÈME : Console warning
```
Warning: Each child in a list should have a unique "key" prop.
```


## Qu'est-ce qu'une key ?

Key = Identifiant unique que React utilise pour suivre chaque élément

```
┌─────────────────────────────────────────────────────────────┐
│  SANS KEY                                                   │
│  React ne sait pas quel élément est lequel                  │
│                                                             │
│  Initial:  [Pomme] [Banane] [Orange]                        │
│  Après:    [Banane] [Orange] [Fraise]                       │
│            v       v         v                              │
│  React:    "Tout a changé! Je re-render tout!"              │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  AVEC KEY                                                   │
│  React sait exactement quel élément est lequel              │
│                                                             │
│  Initial:  [Pomme:1] [Banane:2] [Orange:3]                  │
│  Après:    [Banane:2] [Orange:3] [Fraise:4]                 │
│            v          v          v                          │
│  React:    "Pomme:1 supprimée, 2 et 3 déplacées,            │
│            Fraise:4 ajoutée. Je fais que ça!"               │
└─────────────────────────────────────────────────────────────┘
```


## Comment utiliser les keys

### [OK] BONNE KEY : ID unique et stable

```javascript
const todos = [
  { id: 1, text: 'Faire courses' },
  { id: 2, text: 'Appeler maman' },
  { id: 3, text: 'Coder' }
];

function TodoList() {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
        //      ^
        //      ID unique provenant des données
      ))}
    </ul>
  );
}
```


### [X] MAUVAISE KEY : Index du tableau

```javascript
// [X] À ÉVITER (sauf si liste statique)
{items.map((item, index) => (
  <li key={index}>{item}</li>
))}
```

POURQUOI C'EST MAUVAIS ?

```javascript
// Exemple du problème avec index

// État initial
const items = ['A', 'B', 'C'];
// Rendu: A (key=0), B (key=1), C (key=2)

// Je supprime 'A'
const items = ['B', 'C'];
// Rendu: B (key=0), C (key=1)
//        ^           ^
//        key changée! key changée!

// React pense que:
// - L'élément key=0 a changé de 'A' à 'B'
// - L'élément key=1 a changé de 'B' à 'C'
// - L'élément key=2 a disparu
// -> React fait 3 mises à jour au lieu de 1 !
```


### [OK] Quand l'index est acceptable

```javascript
// [OK] OK si liste JAMAIS modifiée
const daysOfWeek = ['Lundi', 'Mardi', 'Mercredi', ...];

{daysOfWeek.map((day, index) => (
  <li key={index}>{day}</li>
))}

// Liste statique qui ne change jamais -> index OK
```


## Générer des keys uniques

### Si pas d'ID dans les données

```javascript
// Solution 1 : Utiliser une librairie
import { v4 as uuidv4 } from 'uuid';

const newItem = {
  id: uuidv4(),  // Génère: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
  text: 'Nouveau'
};

// Solution 2 : Timestamp + random
const id = `${Date.now()}-${Math.random()}`;

// Solution 3 : Compteur global
let nextId = 0;
const id = nextId++;

// Solution 4 : Combiner champs existants
const id = `${user.email}-${user.name}`;
```


## Keys dans des composants

```javascript
function TodoItem({ todo }) {
  return (
    <div>
      <span>{todo.text}</span>
      <button>Supprimer</button>
    </div>
  );
}

function TodoList({ todos }) {
  return (
    <div>
      {todos.map(todo => (
        // Key sur le composant parent, pas sur les enfants
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </div>
  );
}
```

RÈGLE : La key doit être sur l'élément DIRECTEMENT dans le map


## Erreurs courantes avec les keys

```javascript
// [X] ERREUR : Key sur mauvais élément
{items.map(item => (
  <div>
    <span key={item.id}>{item.text}</span>  {/* [X] */}
  </div>
))}

// [OK] CORRECT : Key sur élément parent
{items.map(item => (
  <div key={item.id}>                      {/* [OK] */}
    <span>{item.text}</span>
  </div>
))}


// [X] ERREUR : Key non unique
{items.map(item => (
  <li key="same-key">{item}</li>  {/* Toutes ont la même key! */}
))}

// [OK] CORRECT : Key unique pour chaque élément
{items.map(item => (
  <li key={item.id}>{item.text}</li>
))}
```


# === RENDU CONDITIONNEL ===

Afficher ou masquer des éléments selon des conditions


## Méthode 1 : Opérateur && (AND logique)

```javascript
// Afficher seulement si la condition est vraie
{condition && <Component />}
```

### Comment ça marche ?

```javascript
// En JavaScript
true && "Bonjour"    // -> "Bonjour"
false && "Bonjour"   // -> false (ne s'affiche pas)

// En JSX
{true && <div>Visible</div>}    // -> <div> s'affiche
{false && <div>Invisible</div>}  // -> Rien ne s'affiche
```

### Exemples

```javascript
function UserGreeting({ isLoggedIn, username }) {
  return (
    <div>
      {/* N'affiche que si connecté */}
      {isLoggedIn && (
        <h1>Bienvenue, {username}!</h1>
      )}
      
      {/* N'affiche que si pas connecté */}
      {!isLoggedIn && (
        <button>Se connecter</button>
      )}
    </div>
  );
}
```


### [ATTENTION] PIÈGE avec les nombres

```javascript
const count = 0;

// [X] PROBLÈME : Affiche "0" !
{count && <div>Il y a {count} items</div>}
// Quand count = 0 -> 0 && <div> -> Affiche "0"

// [OK] SOLUTION : Comparaison explicite
{count > 0 && <div>Il y a {count} items</div>}
// Quand count = 0 -> false && <div> -> N'affiche rien
```

RÈGLE : Toujours utiliser des booléens avec &&, pas des nombres !


## Méthode 2 : Ternaire (if-else)

```javascript
// Syntaxe : condition ? siVrai : siFaux
{condition ? <ComponentA /> : <ComponentB />}
```

### Exemples

```javascript
function StatusMessage({ isOnline }) {
  return (
    <div>
      {isOnline ? (
        <span className="online">[VERT] En ligne</span>
      ) : (
        <span className="offline">[ROUGE] Hors ligne</span>
      )}
    </div>
  );
}


function UserCard({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      
      {/* Afficher âge ou "Non renseigné" */}
      <p>
        Âge : {user.age ? user.age : 'Non renseigné'}
      </p>
      
      {/* Avec nullish coalescing (plus court) */}
      <p>
        Email : {user.email ?? 'Non renseigné'}
      </p>
    </div>
  );
}
```


### Ternaires imbriqués (attention !)

```javascript
// [X] Difficile à lire
{status === 'loading' ? (
  <Spinner />
) : status === 'error' ? (
  <Error />
) : status === 'success' ? (
  <Data />
) : (
  <Empty />
)}

// [OK] Plus lisible : Variable + if-else
function Component({ status }) {
  let content;
  
  if (status === 'loading') {
    content = <Spinner />;
  } else if (status === 'error') {
    content = <Error />;
  } else if (status === 'success') {
    content = <Data />;
  } else {
    content = <Empty />;
  }
  
  return <div>{content}</div>;
}
```


## Méthode 3 : Variable avant le return

```javascript
function ProductPage({ product }) {
  // Toute la logique conditionnelle avant le return
  let priceDisplay;
  
  if (product.discount > 0) {
    const finalPrice = product.price * (1 - product.discount);
    priceDisplay = (
      <div>
        <span className="original-price">{product.price}€</span>
        <span className="final-price">{finalPrice}€</span>
        <span className="discount">-{product.discount * 100}%</span>
      </div>
    );
  } else {
    priceDisplay = <div className="price">{product.price}€</div>;
  }
  
  // JSX simple et lisible
  return (
    <div className="product">
      <h2>{product.name}</h2>
      {priceDisplay}
    </div>
  );
}
```


## Méthode 4 : Fonction helper

```javascript
function UserDashboard({ user }) {
  // Fonction qui retourne le JSX approprié
  function renderUserStatus() {
    if (user.isPremium) {
      return (
        <div className="status premium">
          * Membre Premium
          <button>Gérer abonnement</button>
        </div>
      );
    }
    
    if (user.isTrial) {
      return (
        <div className="status trial">
          [ALARM_CLOCK] Essai gratuit ({user.trialDaysLeft} jours restants)
          <button>Passer Premium</button>
        </div>
      );
    }
    
    return (
      <div className="status free">
        Compte gratuit
        <button>Essayer Premium</button>
      </div>
    );
  }
  
  return (
    <div>
      <h1>Tableau de bord</h1>
      {renderUserStatus()}
    </div>
  );
}
```


## Méthode 5 : Early return

```javascript
function UserProfile({ user }) {
  // Gérer les cas spéciaux en premier
  if (!user) {
    return <div>Chargement...</div>;
  }
  
  if (user.isBlocked) {
    return <div>Compte bloqué</div>;
  }
  
  if (user.isDeleted) {
    return <div>Compte supprimé</div>;
  }
  
  // Cas normal à la fin
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}
```


## Méthode 6 : Objet de mapping

```javascript
function StatusIcon({ status }) {
  // Mapper chaque statut à son icône
  const statusIcons = {
    success: '[OK]',
    error: '[X]',
    warning: '[ATTENTION]',
    info: 'ℹ',
    pending: '[HOURGLASS_WITH_FLOWING_SAND]'
  };
  
  // Récupérer l'icône correspondante
  const icon = statusIcons[status] || '[?]';
  
  return <span className={`status-${status}`}>{icon}</span>;
}


// Mapping plus complexe avec composants
function NotificationItem({ type }) {
  const notificationComponents = {
    message: <MessageNotif />,
    like: <LikeNotif />,
    comment: <CommentNotif />,
    follow: <FollowNotif />
  };
  
  return notificationComponents[type] || <DefaultNotif />;
}
```


# === ÉVÉNEMENTS (EVENTS) ===

Gérer les interactions utilisateur (clics, saisies, soumissions...)


## Syntaxe des événements

```javascript
// ╔═══════════════════════════════════════════════════════════╗
// ║  ÉVÉNEMENTS : HTML vs JSX                                 ║
// ╚═══════════════════════════════════════════════════════════╝

// ═══════════════════════════════════════════════════════════
// HTML (string, minuscule)
// ═══════════════════════════════════════════════════════════
<button onclick="handleClick()">Cliquer</button>


// ═══════════════════════════════════════════════════════════
// JSX (fonction, camelCase)
// ═══════════════════════════════════════════════════════════
<button onClick={handleClick}>Cliquer</button>
//               ^              ^
//               camelCase      fonction (pas string!)
```


## Événements courants

```javascript
function EventsExample() {
  return (
    <div>
      {/* Click */}
      <button onClick={handleClick}>Cliquer</button>
      
      {/* Double click */}
      <div onDoubleClick={handleDoubleClick}>Double-cliquer</div>
      
      {/* Mouse enter/leave */}
      <div 
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
      >
        Survoler
      </div>
      
      {/* Input change */}
      <input onChange={handleChange} />
      
      {/* Focus/Blur */}
      <input 
        onFocus={handleFocus}
        onBlur={handleBlur}
      />
      
      {/* Key press */}
      <input 
        onKeyDown={handleKeyDown}
        onKeyUp={handleKeyUp}
        onKeyPress={handleKeyPress}
      />
      
      {/* Form submit */}
      <form onSubmit={handleSubmit}>
        <button type="submit">Envoyer</button>
      </form>
      
      {/* Scroll */}
      <div onScroll={handleScroll}>Contenu scrollable</div>
    </div>
  );
}
```


## L'objet événement (event object)

```javascript
function handleClick(event) {
  console.log(event);
  //          ^
  //          Objet SyntheticEvent de React
  
  event.preventDefault();    // Empêcher comportement par défaut
  event.stopPropagation();   // Empêcher propagation
  
  console.log(event.target);        // Élément qui a déclenché l'événement
  console.log(event.currentTarget); // Élément sur lequel l'handler est attaché
  console.log(event.type);          // Type d'événement ('click', 'change'...)
}
```


## Passer des paramètres à un handler

### Méthode 1 : Arrow function inline

```javascript
function TodoList({ todos }) {
  const handleDelete = (id) => {
    console.log('Supprimer todo:', id);
  };
  
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          {/* Arrow function pour passer l'id */}
          <button onClick={() => handleDelete(todo.id)}>
            Supprimer
          </button>
        </li>
      ))}
    </ul>
  );
}
```

[ATTENTION] ATTENTION : Crée une nouvelle fonction à chaque render (peut impacter performances)


### Méthode 2 : Bind

```javascript
<button onClick={handleDelete.bind(null, todo.id)}>
  Supprimer
</button>
```


### Méthode 3 : Data attributes

```javascript
function TodoList({ todos }) {
  const handleDelete = (event) => {
    const id = event.target.dataset.id;
    console.log('Supprimer:', id);
  };
  
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          <button data-id={todo.id} onClick={handleDelete}>
            Supprimer
          </button>
        </li>
      ))}
    </ul>
  );
}
```


### Méthode 4 : Fonction qui retourne une fonction

```javascript
function TodoList({ todos }) {
  const handleDelete = (id) => {
    return (event) => {
      console.log('Supprimer:', id);
      console.log('Event:', event);
    };
  };
  
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          <button onClick={handleDelete(todo.id)}>
            Supprimer
          </button>
        </li>
      ))}
    </ul>
  );
}
```


## Empêcher le comportement par défaut

```javascript
// Formulaire : Empêcher rechargement de page
function LoginForm() {
  const handleSubmit = (event) => {
    event.preventDefault();  // <- Important !
    
    console.log('Formulaire soumis');
    // Traiter les données...
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" />
      <input type="password" name="password" />
      <button type="submit">Connexion</button>
    </form>
  );
}


// Lien : Empêcher navigation
function CustomLink() {
  const handleClick = (event) => {
    event.preventDefault();  // N'ouvre pas le lien
    
    console.log('Lien cliqué');
    // Navigation personnalisée...
  };
  
  return (
    <a href="/page" onClick={handleClick}>
      Cliquer ici
    </a>
  );
}
```


## Propagation des événements

```javascript
function EventPropagation() {
  const handleParentClick = () => {
    console.log('Parent cliqué');
  };
  
  const handleChildClick = (event) => {
    console.log('Enfant cliqué');
    
    // Empêcher que l'événement remonte au parent
    event.stopPropagation();
  };
  
  return (
    <div onClick={handleParentClick} style={{ padding: '50px', background: 'lightblue' }}>
      Parent
      
      <button onClick={handleChildClick} style={{ margin: '20px' }}>
        Enfant
      </button>
    </div>
  );
}

// Sans stopPropagation:
// Clic sur bouton -> "Enfant cliqué" puis "Parent cliqué"

// Avec stopPropagation:
// Clic sur bouton -> "Enfant cliqué" seulement
```


## Événements clavier

```javascript
function SearchBox() {
  const [query, setQuery] = useState('');
  
  const handleKeyDown = (event) => {
    console.log('Touche pressée:', event.key);
    
    // Détecter touches spéciales
    if (event.key === 'Enter') {
      console.log('Enter pressé, rechercher:', query);
      performSearch(query);
    }
    
    if (event.key === 'Escape') {
      console.log('Escape pressé, réinitialiser');
      setQuery('');
    }
    
    // Combinaisons de touches
    if (event.ctrlKey && event.key === 's') {
      event.preventDefault();  // Empêcher Ctrl+S de sauvegarder la page
      console.log('Ctrl+S pressé, sauvegarder');
      saveData();
    }
  };
  
  return (
    <input
      type="text"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      onKeyDown={handleKeyDown}
      placeholder="Rechercher... (Enter pour valider)"
    />
  );
}


// Touches communes
event.key === 'Enter'       // Entrée
event.key === 'Escape'      // Échap
event.key === 'ArrowUp'     // Flèche haut
event.key === 'ArrowDown'   // Flèche bas
event.key === 'Tab'         // Tab
event.key === ' '           // Espace

// Modificateurs
event.ctrlKey   // Ctrl enfoncé
event.shiftKey  // Shift enfoncé
event.altKey    // Alt enfoncé
event.metaKey   // Cmd (Mac) / Windows key
```


# === EXEMPLES PRATIQUES COMPLETS ===

## Exemple 1 : Compteur avec plusieurs boutons

```javascript
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div className="counter">
      <h2>Compteur : {count}</h2>
      
      {/* Boutons d'action */}
      <div className="buttons">
        <button onClick={() => setCount(count - 10)}>-10</button>
        <button onClick={() => setCount(count - 1)}>-1</button>
        <button onClick={() => setCount(0)}>Reset</button>
        <button onClick={() => setCount(count + 1)}>+1</button>
        <button onClick={() => setCount(count + 10)}>+10</button>
      </div>
      
      {/* Messages conditionnels */}
      {count === 0 && (
        <p className="info">Le compteur est à zéro</p>
      )}
      
      {count > 0 && (
        <p className="positive">Valeur positive : +{count}</p>
      )}
      
      {count < 0 && (
        <p className="negative">Valeur négative : {count}</p>
      )}
      
      {count >= 100 && (
        <p className="milestone">[BRAVO] Vous avez atteint 100 !</p>
      )}
    </div>
  );
}
```


## Exemple 2 : Liste de fruits avec filtrage

```javascript
import { useState } from 'react';

function FruitList() {
  const [fruits] = useState([
    { id: 1, name: 'Pomme', color: 'rouge', price: 2.5 },
    { id: 2, name: 'Banane', color: 'jaune', price: 1.5 },
    { id: 3, name: 'Orange', color: 'orange', price: 3.0 },
    { id: 4, name: 'Raisin', color: 'violet', price: 4.0 },
    { id: 5, name: 'Fraise', color: 'rouge', price: 5.0 }
  ]);
  
  const [filter, setFilter] = useState('all');
  const [sortBy, setSortBy] = useState('name');
  
  // Filtrer les fruits
  const filteredFruits = fruits.filter(fruit => {
    if (filter === 'all') return true;
    return fruit.color === filter;
  });
  
  // Trier les fruits
  const sortedFruits = [...filteredFruits].sort((a, b) => {
    if (sortBy === 'name') {
      return a.name.localeCompare(b.name);
    }
    if (sortBy === 'price') {
      return a.price - b.price;
    }
    return 0;
  });
  
  return (
    <div className="fruit-list">
      <h2>Liste de fruits</h2>
      
      {/* Filtres */}
      <div className="filters">
        <label>Couleur :</label>
        <select value={filter} onChange={(e) => setFilter(e.target.value)}>
          <option value="all">Toutes</option>
          <option value="rouge">Rouge</option>
          <option value="jaune">Jaune</option>
          <option value="orange">Orange</option>
          <option value="violet">Violet</option>
        </select>
        
        <label>Trier par :</label>
        <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
          <option value="name">Nom</option>
          <option value="price">Prix</option>
        </select>
      </div>
      
      {/* Liste */}
      <ul>
        {sortedFruits.map(fruit => (
          <li key={fruit.id} className={`fruit-${fruit.color}`}>
            <span className="name">{fruit.name}</span>
            <span className="color">({fruit.color})</span>
            <span className="price">{fruit.price}€</span>
          </li>
        ))}
      </ul>
      
      {/* Statistiques */}
      <div className="stats">
        <p>Total : {sortedFruits.length} fruits</p>
        <p>
          Prix moyen : {
            sortedFruits.length > 0
              ? (sortedFruits.reduce((sum, f) => sum + f.price, 0) / sortedFruits.length).toFixed(2)
              : 0
          }€
        </p>
      </div>
      
      {/* Message si liste vide */}
      {sortedFruits.length === 0 && (
        <p className="empty">Aucun fruit ne correspond aux filtres</p>
      )}
    </div>
  );
}
```


## Exemple 3 : Formulaire de contact complet

```javascript
import { useState } from 'react';

function ContactForm() {
  // États du formulaire
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    subject: '',
    message: '',
    newsletter: false
  });
  
  // États de validation
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  
  // État de soumission
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submitSuccess, setSubmitSuccess] = useState(false);
  
  // Gérer les changements
  const handleChange = (event) => {
    const { name, value, type, checked } = event.target;
    
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
    
    // Valider le champ si déjà touché
    if (touched[name]) {
      validateField(name, type === 'checkbox' ? checked : value);
    }
  };
  
  // Gérer le blur (perte de focus)
  const handleBlur = (event) => {
    const { name, value } = event.target;
    
    setTouched(prev => ({
      ...prev,
      [name]: true
    }));
    
    validateField(name, value);
  };
  
  // Valider un champ
  const validateField = (name, value) => {
    let error = '';
    
    switch (name) {
      case 'name':
        if (!value || value.trim() === '') {
          error = 'Le nom est requis';
        } else if (value.length < 2) {
          error = 'Le nom doit faire au moins 2 caractères';
        }
        break;
        
      case 'email':
        if (!value || value.trim() === '') {
          error = 'L\'email est requis';
        } else if (!/\S+@\S+\.\S+/.test(value)) {
          error = 'L\'email n\'est pas valide';
        }
        break;
        
      case 'subject':
        if (!value || value.trim() === '') {
          error = 'Le sujet est requis';
        }
        break;
        
      case 'message':
        if (!value || value.trim() === '') {
          error = 'Le message est requis';
        } else if (value.length < 10) {
          error = 'Le message doit faire au moins 10 caractères';
        }
        break;
        
      default:
        break;
    }
    
    setErrors(prev => ({
      ...prev,
      [name]: error
    }));
    
    return error === '';
  };
  
  // Valider tout le formulaire
  const validateForm = () => {
    const newErrors = {};
    
    if (!formData.name.trim()) {
      newErrors.name = 'Le nom est requis';
    }
    
    if (!formData.email.trim()) {
      newErrors.email = 'L\'email est requis';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'L\'email n\'est pas valide';
    }
    
    if (!formData.subject.trim()) {
      newErrors.subject = 'Le sujet est requis';
    }
    
    if (!formData.message.trim()) {
      newErrors.message = 'Le message est requis';
    } else if (formData.message.length < 10) {
      newErrors.message = 'Le message doit faire au moins 10 caractères';
    }
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  // Soumettre le formulaire
  const handleSubmit = async (event) => {
    event.preventDefault();
    
    // Marquer tous les champs comme touchés
    setTouched({
      name: true,
      email: true,
      subject: true,
      message: true
    });
    
    // Valider
    if (!validateForm()) {
      return;
    }
    
    // Soumettre
    setIsSubmitting(true);
    
    try {
      // Simuler appel API
      await new Promise(resolve => setTimeout(resolve, 2000));
      
      console.log('Formulaire soumis:', formData);
      
      setSubmitSuccess(true);
      
      // Réinitialiser le formulaire
      setFormData({
        name: '',
        email: '',
        subject: '',
        message: '',
        newsletter: false
      });
      setTouched({});
      setErrors({});
      
    } catch (error) {
      console.error('Erreur:', error);
    } finally {
      setIsSubmitting(false);
    }
  };
  
  return (
    <div className="contact-form">
      <h2>Formulaire de contact</h2>
      
      {/* Message de succès */}
      {submitSuccess && (
        <div className="success-message">
          [OK] Votre message a été envoyé avec succès !
        </div>
      )}
      
      <form onSubmit={handleSubmit}>
        {/* Nom */}
        <div className="form-group">
          <label htmlFor="name">
            Nom <span className="required">*</span>
          </label>
          <input
            id="name"
            name="name"
            type="text"
            value={formData.name}
            onChange={handleChange}
            onBlur={handleBlur}
            className={touched.name && errors.name ? 'error' : ''}
          />
          {touched.name && errors.name && (
            <span className="error-message">{errors.name}</span>
          )}
        </div>
        
        {/* Email */}
        <div className="form-group">
          <label htmlFor="email">
            Email <span className="required">*</span>
          </label>
          <input
            id="email"
            name="email"
            type="email"
            value={formData.email}
            onChange={handleChange}
            onBlur={handleBlur}
            className={touched.email && errors.email ? 'error' : ''}
          />
          {touched.email && errors.email && (
            <span className="error-message">{errors.email}</span>
          )}
        </div>
        
        {/* Sujet */}
        <div className="form-group">
          <label htmlFor="subject">
            Sujet <span className="required">*</span>
          </label>
          <select
            id="subject"
            name="subject"
            value={formData.subject}
            onChange={handleChange}
            onBlur={handleBlur}
            className={touched.subject && errors.subject ? 'error' : ''}
          >
            <option value="">Choisir un sujet</option>
            <option value="info">Demande d'information</option>
            <option value="support">Support technique</option>
            <option value="partnership">Partenariat</option>
            <option value="other">Autre</option>
          </select>
          {touched.subject && errors.subject && (
            <span className="error-message">{errors.subject}</span>
          )}
        </div>
        
        {/* Message */}
        <div className="form-group">
          <label htmlFor="message">
            Message <span className="required">*</span>
          </label>
          <textarea
            id="message"
            name="message"
            rows={5}
            value={formData.message}
            onChange={handleChange}
            onBlur={handleBlur}
            className={touched.message && errors.message ? 'error' : ''}
          />
          <div className="char-count">
            {formData.message.length} / 500 caractères
          </div>
          {touched.message && errors.message && (
            <span className="error-message">{errors.message}</span>
          )}
        </div>
        
        {/* Newsletter */}
        <div className="form-group checkbox">
          <label>
            <input
              type="checkbox"
              name="newsletter"
              checked={formData.newsletter}
              onChange={handleChange}
            />
            <span>S'abonner à la newsletter</span>
          </label>
        </div>
        
        {/* Bouton submit */}
        <button
          type="submit"
          className="submit-button"
          disabled={isSubmitting}
        >
          {isSubmitting ? '[HOURGLASS_WITH_FLOWING_SAND] Envoi en cours...' : '[EMAIL] Envoyer'}
        </button>
      </form>
    </div>
  );
}
```


# === PIÈGES COURANTS ET ERREURS ===

## Piège 1 : Oublier les accolades pour JavaScript

```javascript
// [X] ERREUR : name est affiché littéralement
<h1>Bonjour name</h1>
// Affiche : "Bonjour name"

// [OK] CORRECT : name est évalué
<h1>Bonjour {name}</h1>
// Affiche : "Bonjour Alice"
```


## Piège 2 : Utiliser class au lieu de className

```javascript
// [X] ERREUR : Warning dans la console
<div class="container"></div>

// [OK] CORRECT
<div className="container"></div>
```


## Piège 3 : Oublier de fermer les balises

```javascript
// [X] ERREUR : Syntax error
<input type="text">
<img src="photo.jpg">

// [OK] CORRECT
<input type="text" />
<img src="photo.jpg" />
```


## Piège 4 : Retourner plusieurs éléments racines

```javascript
// [X] ERREUR
return (
  <h1>Titre</h1>
  <p>Paragraphe</p>
);

// [OK] SOLUTION 1 : Wrapper div
return (
  <div>
    <h1>Titre</h1>
    <p>Paragraphe</p>
  </div>
);

// [OK] SOLUTION 2 : Fragment
return (
  <>
    <h1>Titre</h1>
    <p>Paragraphe</p>
  </>
);
```


## Piège 5 : Utiliser && avec des nombres

```javascript
const count = 0;

// [X] PROBLÈME : Affiche "0"
{count && <div>Items: {count}</div>}

// [OK] CORRECT : Comparaison booléenne
{count > 0 && <div>Items: {count}</div>}
```


## Piège 6 : Oublier la key dans les listes

```javascript
// [X] WARNING : Each child should have a unique "key"
{items.map(item => (
  <li>{item}</li>
))}

// [OK] CORRECT
{items.map(item => (
  <li key={item.id}>{item.text}</li>
))}
```


## Piège 7 : Utiliser index comme key dans liste dynamique

```javascript
// [X] PEUT CAUSER DES BUGS
{items.map((item, index) => (
  <li key={index}>{item}</li>
))}

// [OK] PRÉFÉRER : ID unique
{items.map(item => (
  <li key={item.id}>{item.text}</li>
))}
```


## Piège 8 : Styles en string au lieu d'objet

```javascript
// [X] ERREUR
<div style="color: red; font-size: 20px;">Text</div>

// [OK] CORRECT : Objet JavaScript
<div style={{ color: 'red', fontSize: '20px' }}>Text</div>
```


## Piège 9 : Appeler la fonction au lieu de la passer

```javascript
// [X] ERREUR : Fonction appelée immédiatement à chaque render
<button onClick={handleClick()}>Cliquer</button>

// [OK] CORRECT : Référence à la fonction
<button onClick={handleClick}>Cliquer</button>

// [OK] CORRECT : Arrow function si paramètres
<button onClick={() => handleClick(id)}>Cliquer</button>
```


## Piège 10 : Commentaires HTML dans JSX

```javascript
// [X] ERREUR : Commentaire HTML ne fonctionne pas
<div>
  <!-- Ceci ne fonctionne pas -->
  <p>Texte</p>
</div>

// [OK] CORRECT : Commentaire JSX
<div>
  {/* Ceci fonctionne */}
  <p>Texte</p>
</div>
```


# === BONNES PRATIQUES JSX ===

## 1. Indentation et lisibilité

```javascript
// [X] Difficile à lire
<div><h1>Titre</h1><p>Paragraphe</p><button>Cliquer</button></div>

// [OK] Bien indenté
<div>
  <h1>Titre</h1>
  <p>Paragraphe</p>
  <button>Cliquer</button>
</div>
```


## 2. Parenthèses pour JSX multi-lignes

```javascript
// [X] Peut causer des problèmes (automatic semicolon insertion)
return
  <div>
    <h1>Titre</h1>
  </div>

// [OK] Avec parenthèses
return (
  <div>
    <h1>Titre</h1>
  </div>
);
```


## 3. Extraire JSX complexe dans des variables

```javascript
// [X] JSX trop complexe inline
return (
  <div>
    {user.isPremium ? (
      <div className="premium">
        <span className="badge">* Premium</span>
        <span className="expiry">Expire le {user.expiryDate}</span>
        <button onClick={manageSub}>Gérer</button>
      </div>
    ) : (
      <div className="free">
        <span className="badge">Free</span>
        <button onClick={upgrade}>Passer Premium</button>
      </div>
    )}
  </div>
);

// [OK] Extrait dans des variables
const premiumBadge = (
  <div className="premium">
    <span className="badge">* Premium</span>
    <span className="expiry">Expire le {user.expiryDate}</span>
    <button onClick={manageSub}>Gérer</button>
  </div>
);

const freeBadge = (
  <div className="free">
    <span className="badge">Free</span>
    <button onClick={upgrade}>Passer Premium</button>
  </div>
);

return (
  <div>
    {user.isPremium ? premiumBadge : freeBadge}
  </div>
);
```


## 4. Composants pour réutilisabilité

```javascript
// [X] Répétition de code
<div>
  <div className="card">
    <h3>Produit 1</h3>
    <p>Description 1</p>
    <button>Acheter</button>
  </div>
  <div className="card">
    <h3>Produit 2</h3>
    <p>Description 2</p>
    <button>Acheter</button>
  </div>
</div>

// [OK] Composant réutilisable
function ProductCard({ title, description }) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <p>{description}</p>
      <button>Acheter</button>
    </div>
  );
}

<div>
  <ProductCard title="Produit 1" description="Description 1" />
  <ProductCard title="Produit 2" description="Description 2" />
</div>
```


## 5. Noms de props descriptifs

```javascript
// [X] Props peu clairs
<Button txt="Save" col="blue" fn={save} />

// [OK] Props descriptifs
<Button text="Save" color="blue" onClick={save} />
```


## 6. Déstructuration des props

```javascript
// [X] Répétitif
function UserCard(props) {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>{props.email}</p>
      <p>{props.age} ans</p>
    </div>
  );
}

// [OK] Déstructuré
function UserCard({ name, email, age }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
      <p>{age} ans</p>
    </div>
  );
}
```


# === RÉCAPITULATIF DES RÈGLES JSX ===

```
╔════════════════════════════════════════════════════════════╗
║  RÈGLES ESSENTIELLES DU JSX                                ║
╚════════════════════════════════════════════════════════════╝

1. JavaScript dans JSX
   -> Utiliser {} pour insérer du JavaScript
   -> Seulement des EXPRESSIONS (pas de statements)

2. Attributs en camelCase
   -> class -> className
   -> for -> htmlFor
   -> onclick -> onClick

3. Styles en objet JavaScript
   -> style={{ color: 'red', fontSize: '20px' }}
   -> Propriétés en camelCase (backgroundColor)
   -> Valeurs en string ('20px') ou number (20)

4. Fermer toutes les balises
   -> <input type="text" />
   -> <img src="photo.jpg" />
   -> <br />

5. Un seul élément racine
   -> Utiliser <div> ou <>...</>
   -> Fragment préféré pour pas de wrapper

6. Keys dans les listes
   -> <li key={item.id}>{item.text}</li>
   -> ID unique et stable (pas index si dynamique)

7. Commentaires spéciaux
   -> {/* Commentaire JSX */}
   -> Pas de <!-- --> HTML

8. Événements
   -> onClick={handleClick} (fonction, pas string)
   -> event.preventDefault() pour annuler comportement
   -> event.stopPropagation() pour empêcher propagation
```


# === DIFFÉRENCES JSX vs HTML - TABLEAU COMPLET ===

```
╔════════════════════════════════════════════════════════════╗
║  HTML                  ->  JSX                              ║
╚════════════════════════════════════════════════════════════╝

Attributs
─────────────────────────────────────────────────────────────
class                  ->  className
for                    ->  htmlFor
tabindex               ->  tabIndex
readonly               ->  readOnly
maxlength              ->  maxLength
checked                ->  checked (booléen ou {true/false})
selected               ->  selected (contrôlé par value)

Événements
─────────────────────────────────────────────────────────────
onclick="fn()"         ->  onClick={fn}
onchange="fn()"        ->  onChange={fn}
onsubmit="fn()"        ->  onSubmit={fn}
onfocus="fn()"         ->  onFocus={fn}
onblur="fn()"          ->  onBlur={fn}
onkeydown="fn()"       ->  onKeyDown={fn}

Styles
─────────────────────────────────────────────────────────────
style="color: red"     ->  style={{ color: 'red' }}
font-size              ->  fontSize
background-color       ->  backgroundColor
margin-top             ->  marginTop

Balises
─────────────────────────────────────────────────────────────
<input type="text">    ->  <input type="text" />
<img src="...">        ->  <img src="..." />
<br>                   ->  <br />
<hr>                   ->  <hr />

Commentaires
─────────────────────────────────────────────────────────────
<!-- Comment -->       ->  {/* Comment */}

Valeurs booléennes
─────────────────────────────────────────────────────────────
<input disabled>       ->  <input disabled />
                          <input disabled={true} />
                          <input disabled={false} />

Valeurs JavaScript
─────────────────────────────────────────────────────────────
<div>Texte</div>       ->  <div>Texte</div>
                          <div>{variable}</div>
                          <div>{2 + 2}</div>
```


# === EXERCICES PRATIQUES ===

## Exercice 1 : Convertir HTML en JSX

Convertissez ce HTML en JSX valide :

```html
<!-- HTML -->
<div class="container" style="padding: 20px; background-color: blue;">
  <h1 onclick="alert('Hello')">Bienvenue</h1>
  <label for="email">Email:</label>
  <input type="email" id="email" readonly>
  <img src="photo.jpg">
  <br>
  <!-- Commentaire -->
</div>
```

SOLUTION :

```javascript
// JSX
<div className="container" style={{ padding: '20px', backgroundColor: 'blue' }}>
  <h1 onClick={() => alert('Hello')}>Bienvenue</h1>
  <label htmlFor="email">Email:</label>
  <input type="email" id="email" readOnly />
  <img src="photo.jpg" />
  <br />
  {/* Commentaire */}
</div>
```


## Exercice 2 : Afficher une liste avec filtrage

Créez un composant qui affiche une liste de noms avec un filtre :

```javascript
function NameList() {
  const names = ['Alice', 'Bob', 'Charlie', 'David', 'Eve'];
  const [filter, setFilter] = useState('');
  
  // TODO : Implémenter le filtrage et l'affichage
}
```

SOLUTION :

```javascript
function NameList() {
  const names = ['Alice', 'Bob', 'Charlie', 'David', 'Eve'];
  const [filter, setFilter] = useState('');
  
  const filteredNames = names.filter(name =>
    name.toLowerCase().includes(filter.toLowerCase())
  );
  
  return (
    <div>
      <input
        type="text"
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filtrer les noms..."
      />
      
      <ul>
        {filteredNames.map((name, index) => (
          <li key={index}>{name}</li>
        ))}
      </ul>
      
      {filteredNames.length === 0 && (
        <p>Aucun nom ne correspond au filtre</p>
      )}
      
      <p>
        {filteredNames.length} nom(s) affiché(s) sur {names.length}
      </p>
    </div>
  );
}
```


## Exercice 3 : Calculatrice simple

Créez une calculatrice qui peut additionner deux nombres :

```javascript
function Calculator() {
  // TODO : Implémenter
}
```

SOLUTION :

```javascript
function Calculator() {
  const [num1, setNum1] = useState('');
  const [num2, setNum2] = useState('');
  const [result, setResult] = useState(null);
  
  const handleCalculate = () => {
    const n1 = parseFloat(num1);
    const n2 = parseFloat(num2);
    
    if (isNaN(n1) || isNaN(n2)) {
      alert('Veuillez entrer des nombres valides');
      return;
    }
    
    setResult(n1 + n2);
  };
  
  const handleReset = () => {
    setNum1('');
    setNum2('');
    setResult(null);
  };
  
  return (
    <div className="calculator">
      <h2>Calculatrice</h2>
      
      <div className="inputs">
        <input
          type="number"
          value={num1}
          onChange={(e) => setNum1(e.target.value)}
          placeholder="Nombre 1"
        />
        
        <span>+</span>
        
        <input
          type="number"
          value={num2}
          onChange={(e) => setNum2(e.target.value)}
          placeholder="Nombre 2"
        />
      </div>
      
      <div className="buttons">
        <button onClick={handleCalculate}>Calculer</button>
        <button onClick={handleReset}>Réinitialiser</button>
      </div>
      
      {result !== null && (
        <div className="result">
          <strong>Résultat :</strong> {result}
        </div>
      )}
    </div>
  );
}
```


# === RESSOURCES SUPPLÉMENTAIRES ===

## Documentation officielle
- React Docs (JSX) : https://react.dev/learn/writing-markup-with-jsx
- React Docs (JavaScript in JSX) : https://react.dev/learn/javascript-in-jsx-with-curly-braces

## Outils en ligne
- Babel REPL : https://babeljs.io/repl
  -> Voir comment le JSX est transformé en JavaScript
  
- HTML to JSX Compiler : https://transform.tools/html-to-jsx
  -> Convertir du HTML en JSX automatiquement

## Extensions VSCode
- ES7+ React/Redux/React-Native snippets
  -> Raccourcis pour écrire du JSX rapidement
  
- Prettier
  -> Formater automatiquement le JSX


# === CONCLUSION ===

Félicitations ! Vous maîtrisez maintenant le JSX :

[OK] Comprendre ce qu'est le JSX et pourquoi il existe
[OK] Différences entre HTML et JSX
[OK] Utiliser {} pour insérer du JavaScript
[OK] Comprendre les expressions vs statements
[OK] Convertir les attributs HTML en JSX (className, style...)
[OK] Gérer les listes avec map et keys
[OK] Faire du rendu conditionnel (&&, ternaire, variables)
[OK] Gérer les événements (onClick, onChange, onSubmit...)
[OK] Éviter les pièges courants
[OK] Appliquer les bonnes pratiques


Le JSX est la FONDATION de React. Maîtrisez-le bien et le reste suivra ! [RAPIDE]



[OK] HOOKS - useState (GESTION D'ÉTAT)


useState est un Hook qui permet d'ajouter de l'état local à un composant fonctionnel.
L'état = données qui peuvent changer au fil du temps (compteur, formulaire, etc.)

IMPORTANT: Les Hooks ne peuvent être utilisés que dans des composants fonctionnels,
pas dans des fonctions normales ou des composants classe.


# === Concept fondamental ===

import { useState } from 'react';

// Syntaxe de base
const [variable, setVariable] = useState(valeurInitiale);
//     ^          ^               ^
//     état       fonction         valeur de départ
//     actuel     pour modifier

// ANALOGIE Python:
// class Component:
//     def __init__(self):
//         self.count = 0          # état
//     
//     def set_count(self, value):
//         self.count = value       # modifier état
//         self.render()            # re-render automatique en React


// Exemple: Compteur simple
function Counter() {
  // Déclare une variable d'état "count" avec valeur initiale 0
  const [count, setCount] = useState(0);
  
  // Quand on clique, on incrémente count
  // React détecte le changement et re-render automatiquement le composant
  return (
    <div>
      <p>Vous avez cliqué {count} fois</p>
      <button onClick={() => setCount(count + 1)}>
        Cliquer
      </button>
    </div>
  );
}

// FLUX D'EXÉCUTION:
// 1. Composant s'affiche avec count = 0
// 2. Utilisateur clique sur le bouton
// 3. setCount(1) est appelé
// 4. React détecte le changement d'état
// 5. React re-render le composant avec count = 1
// 6. L'interface se met à jour automatiquement


// États de différents types
function Examples() {
  const [count, setCount] = useState(0);                    // nombre
  const [name, setName] = useState('');                     // string
  const [isActive, setIsActive] = useState(false);          // boolean
  const [items, setItems] = useState([]);                   // tableau
  const [user, setUser] = useState(null);                   // objet/null
  const [config, setConfig] = useState({ theme: 'dark' });  // objet
  
  return <div>...</div>;
}


// Lazy initialization (pour calculs coûteux)
// [X] Mauvais: computeInitialValue() est appelé à CHAQUE render
const [data, setData] = useState(computeInitialValue());

// [OK] Bon: computeInitialValue() est appelé UNE SEULE fois
const [data, setData] = useState(() => computeInitialValue());

// EXPLICATION:
// - Passer une fonction à useState
// - La fonction s'exécute seulement au premier render
// - Utile pour calculs coûteux ou lecture localStorage


// Exemple lazy initialization
function ExpensiveComponent() {
  const [data, setData] = useState(() => {
    // Ceci s'exécute UNE SEULE fois
    console.log('Calcul initial coûteux...');
    const result = [];
    for (let i = 0; i < 10000; i++) {
      result.push(Math.random());
    }
    return result;
  });
  
  return <div>Data length: {data.length}</div>;
}


# === Mise à jour de l'état ===

// Mise à jour simple
const [count, setCount] = useState(0);
setCount(5);        // count devient 5
setCount(count + 1); // count devient 6

// [ATTENTION] PIÈGE: Mise à jour asynchrone
function handleClick() {
  setCount(count + 1);  // count = 1
  setCount(count + 1);  // count = 1 (pas 2!)
  console.log(count);   // Affiche toujours 0 (pas encore mis à jour)
}

// POURQUOI:
// - setState est asynchrone (batching pour performance)
// - Plusieurs setState sont groupés ensemble
// - count n'est mis à jour qu'après le re-render


// [OK] SOLUTION: Fonction de mise à jour (accès à valeur précédente)
function handleClick() {
  setCount(prev => prev + 1);  // prev = valeur actuelle de count
  setCount(prev => prev + 1);  // prev = valeur mise à jour
  // Résultat: count += 2 [OK]
}

// RÈGLE: Toujours utiliser la forme fonctionnelle si la nouvelle valeur
// dépend de la valeur précédente

// [X] Peut être incorrect avec batching
setCount(count + 1);

// [OK] Toujours correct
setCount(prev => prev + 1);


# === État avec objets (IMMUTABILITÉ CRUCIALE) ===

const [user, setUser] = useState({
  name: 'John',
  age: 30,
  email: 'john@example.com'
});

// [X] MAUVAIS: Mutation directe (React ne détecte PAS le changement)
user.age = 31;
setUser(user);  // React pense que rien n'a changé (même référence)

// [OK] BON: Créer un nouvel objet (spread operator)
setUser({ ...user, age: 31 });

// EXPLICATION du spread operator:
// { ...user, age: 31 } crée:
// {
//   name: 'John',      // copié de user
//   age: 31,           // remplacé
//   email: 'john@example.com'  // copié de user
// }

// Avec fonction de mise à jour (recommandé)
setUser(prev => ({ ...prev, age: 31 }));

// Modifier plusieurs champs
setUser(prev => ({
  ...prev,
  age: 31,
  email: 'newemail@example.com'
}));

// ANALOGIE Python (dataclass):
// from dataclasses import dataclass, replace
// user = replace(user, age=31)


# === État avec tableaux (IMMUTABILITÉ) ===

const [items, setItems] = useState([1, 2, 3]);

// [X] MAUVAIS: Mutation directe
items.push(4);
setItems(items);  // React ne détecte pas le changement!

// [OK] BON: Créer nouveau tableau

// Ajouter à la fin
setItems([...items, 4]);
setItems(prev => [...prev, 4]);

// Ajouter au début
setItems([0, ...items]);

// Supprimer par index
setItems(items.filter((_, index) => index !== indexToRemove));

// Supprimer par valeur
setItems(items.filter(item => item.id !== idToRemove));

// Modifier un élément
setItems(items.map(item => 
  item.id === targetId 
    ? { ...item, completed: true }  // Modifie cet élément
    : item                           // Garde les autres
));

// Exemple complet: Todo list
function TodoList() {
  const [todos, setTodos] = useState([]);
  
  const addTodo = (text) => {
    const newTodo = {
      id: Date.now(),
      text: text,
      completed: false
    };
    setTodos(prev => [...prev, newTodo]);
  };
  
  const toggleTodo = (id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id 
        ? { ...todo, completed: !todo.completed }
        : todo
    ));
  };
  
  const deleteTodo = (id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  };
  
  return (
    <div>
      {todos.map(todo => (
        <div key={todo.id}>
          <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
          <button onClick={() => toggleTodo(todo.id)}>Toggle</button>
          <button onClick={() => deleteTodo(todo.id)}>Delete</button>
        </div>
      ))}
    </div>
  );
}


# === Multiples états vs état groupé ===

// Approche 1: États séparés (si indépendants)
function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);
  
  return (
    <form>
      <input value={name} onChange={e => setName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <input value={age} onChange={e => setAge(Number(e.target.value))} />
    </form>
  );
}

// Approche 2: État groupé (si données liées)
function Form() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: 0
  });
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value  // [name] = computed property (ES6)
    }));
  };
  
  return (
    <form>
      <input 
        name="name" 
        value={formData.name} 
        onChange={handleChange} 
      />
      <input 
        name="email" 
        value={formData.email} 
        onChange={handleChange} 
      />
      <input 
        name="age" 
        value={formData.age} 
        onChange={handleChange} 
      />
    </form>
  );
}

// QUAND UTILISER QUOI:
// [OK] États séparés si:
//   - Données indépendantes
//   - Mises à jour séparées
//   - Exemple: theme, isOpen, currentUser
//
// [OK] État groupé si:
//   - Données liées logiquement
//   - Mises à jour ensemble
//   - Exemple: formulaire, configuration


# === Exemple complet: Formulaire de contact ===

function ContactForm() {
  // État du formulaire
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: ''
  });
  
  // État de soumission
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submitSuccess, setSubmitSuccess] = useState(false);
  
  // Gestion du changement des inputs
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };
  
  // Soumission du formulaire
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    setIsSubmitting(true);
    
    try {
      // Envoyer au backend (on verra plus tard)
      await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData)
      });
      
      setSubmitSuccess(true);
      // Réinitialiser le formulaire
      setFormData({ name: '', email: '', message: '' });
    } catch (error) {
      console.error('Erreur:', error);
    } finally {
      setIsSubmitting(false);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="Nom"
        required
      />
      
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
        required
      />
      
      <textarea
        name="message"
        value={formData.message}
        onChange={handleChange}
        placeholder="Message"
        required
      />
      
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Envoi...' : 'Envoyer'}
      </button>
      
      {submitSuccess && <p>Message envoyé avec succès!</p>}
    </form>
  );
}



[OK] HOOKS - useEffect (EFFETS DE BORD)


useEffect permet d'exécuter du code après le render (effets de bord):
- Fetch de données (API calls)
- Abonnements (websockets, events)
- Manipulation DOM directe
- Timers (setTimeout, setInterval)

RÈGLE: Le render doit être pur. Les effets de bord vont dans useEffect.


# === Syntaxe et concepts ===

import { useEffect } from 'react';

// Syntaxe de base
useEffect(() => {
  // Code à exécuter après chaque render
  console.log('Composant rendu');
});

// Avec cleanup (nettoyage)
useEffect(() => {
  // Code d'initialisation
  console.log('Effect exécuté');
  
  return () => {
    // Code de nettoyage (avant destruction ou re-exécution)
    console.log('Cleanup exécuté');
  };
});

// Avec tableau de dépendances
useEffect(() => {
  console.log('Effect exécuté');
}, [dep1, dep2]);  // S'exécute seulement si dep1 ou dep2 changent


# === Les 3 cas d'utilisation ===

// CAS 1: Sans dépendances (s'exécute après CHAQUE render)
useEffect(() => {
  console.log('Après chaque render');
});

// ATTENTION: Rarement utilisé, souvent une erreur
// Peut causer des boucles infinies


// CAS 2: Avec tableau vide [] (s'exécute UNE SEULE fois au mount)
useEffect(() => {
  console.log('Composant monté (équivalent componentDidMount)');
}, []);

// UTILISATION:
// - Fetch de données initiales
// - Initialisation d'abonnements
// - Mesures DOM initiales


// CAS 3: Avec dépendances (s'exécute quand dépendances changent)
useEffect(() => {
  console.log('userId a changé:', userId);
}, [userId]);

// S'exécute:
// 1. Au premier render
// 2. Quand userId change


# === Cleanup (nettoyage) ===

// IMPORTANT: Toujours nettoyer les ressources pour éviter memory leaks

useEffect(() => {
  // Abonnement
  const subscription = api.subscribe(data => {
    console.log(data);
  });
  
  // Cleanup: Désabonnement
  return () => {
    subscription.unsubscribe();
  };
}, []);

// QUAND LE CLEANUP S'EXÉCUTE:
// 1. Avant re-exécution de l'effect (si dépendances changent)
// 2. Avant destruction du composant (unmount)


// Exemple: Event listener
useEffect(() => {
  function handleResize() {
    console.log('Window resized');
  }
  
  // Ajouter l'event listener
  window.addEventListener('resize', handleResize);
  
  // Cleanup: Retirer l'event listener
  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []);


// Exemple: Timer
useEffect(() => {
  const timer = setTimeout(() => {
    console.log('Delayed action');
  }, 1000);
  
  // Cleanup: Annuler le timer
  return () => {
    clearTimeout(timer);
  };
}, []);


// Exemple: Interval
function Clock() {
  const [time, setTime] = useState(new Date());
  
  useEffect(() => {
    const interval = setInterval(() => {
      setTime(new Date());
    }, 1000);
    
    // Cleanup: Arrêter l'interval
    return () => {
      clearInterval(interval);
    };
  }, []);  // Tableau vide = s'exécute une fois
  
  return <div>{time.toLocaleTimeString()}</div>;
}


# === Fetch de données (Pattern courant) ===

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    // Flag pour éviter setState sur composant démonté
    let cancelled = false;
    
    async function fetchUser() {
      try {
        setLoading(true);
        setError(null);
        
        const response = await fetch(`/api/users/${userId}`);
        
        if (!response.ok) {
          throw new Error('Failed to fetch user');
        }
        
        const data = await response.json();
        
        // Vérifier si le composant est toujours monté
        if (!cancelled) {
          setUser(data);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }
    
    fetchUser();
    
    // Cleanup: Marquer comme cancelled si composant démonté
    return () => {
      cancelled = true;
    };
  }, [userId]);  // Re-fetch si userId change
  
  if (loading) return <div>Chargement...</div>;
  if (error) return <div>Erreur: {error}</div>;
  if (!user) return <div>Utilisateur introuvable</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

// EXPLICATION DU PATTERN:
// 1. Trois états: loading, error, data
// 2. Flag 'cancelled' pour éviter setState sur composant démonté
// 3. Try-catch pour gestion d'erreurs
// 4. Dépendance userId: re-fetch si changement


// Avec AbortController (méthode moderne pour annuler fetch)
useEffect(() => {
  const abortController = new AbortController();
  
  async function fetchUser() {
    try {
      const response = await fetch(`/api/users/${userId}`, {
        signal: abortController.signal  // Permet d'annuler
      });
      const data = await response.json();
      setUser(data);
    } catch (err) {
      if (err.name !== 'AbortError') {
        setError(err.message);
      }
    }
  }
  
  fetchUser();
  
  // Cleanup: Annuler la requête en cours
  return () => {
    abortController.abort();
  };
}, [userId]);


# === Dépendances exhaustives (RÈGLE IMPORTANTE) ===

// [OK] BON: Toutes les valeurs utilisées sont dans les dépendances
useEffect(() => {
  console.log(count, user);
}, [count, user]);

// [X] MAUVAIS: Dépendances manquantes
useEffect(() => {
  console.log(count, user);
}, [count]);  // user manquant! Bug potentiel

// RÈGLE: Toute valeur du scope du composant utilisée dans useEffect
// doit être dans le tableau de dépendances

// ESLint plugin 'react-hooks/exhaustive-deps' aide à détecter ces erreurs


// Exemple: Fonction dans les dépendances
function Component({ userId }) {
  // Problème: handleFetch est recréé à chaque render
  function handleFetch() {
    fetch(`/api/users/${userId}`);
  }
  
  useEffect(() => {
    handleFetch();
  }, [handleFetch]);  // handleFetch change à chaque render -> boucle!
}

// Solution 1: Définir la fonction dans useEffect
function Component({ userId }) {
  useEffect(() => {
    function handleFetch() {
      fetch(`/api/users/${userId}`);
    }
    handleFetch();
  }, [userId]);  // Seulement userId dans les dépendances
}

// Solution 2: useCallback (on verra plus tard)
const handleFetch = useCallback(() => {
  fetch(`/api/users/${userId}`);
}, [userId]);

useEffect(() => {
  handleFetch();
}, [handleFetch]);


# === Multiples useEffect (Séparation des préoccupations) ===

// [OK] BON: Un useEffect par préoccupation logique
function UserDashboard({ userId }) {
  // Effect 1: Fetch user data
  useEffect(() => {
    fetchUser(userId);
  }, [userId]);
  
  // Effect 2: Track analytics
  useEffect(() => {
    trackPageView('dashboard');
  }, []);
  
  // Effect 3: Setup websocket
  useEffect(() => {
    const ws = new WebSocket('ws://...');
    return () => ws.close();
  }, []);
}

// [X] MAUVAIS: Tout dans un seul useEffect
function UserDashboard({ userId }) {
  useEffect(() => {
    fetchUser(userId);
    trackPageView('dashboard');
    const ws = new WebSocket('ws://...');
    return () => ws.close();
  }, [userId]);  // Logique mélangée, difficile à maintenir
}



[WEB] CONSOMMATION D'API BACKEND (REACT <-> FLASK)


Architecture typique d'une application React + Flask:

┌──────────────────────────────────────────────────────────────────────────┐
│  FRONTEND (React)                    BACKEND (Flask)                     │
│  Port 3000 (dev)                     Port 5000                           │
│                                                                          │
│  ┌─────────────┐                    ┌─────────────┐                      │
│  │   Browser   │────HTTP Request───->│    Flask    │                      │
│  │   (React)   │                    │   Server    │                      │
│  │             │<-───JSON Response───│             │                      │
│  └─────────────┘                    └─────────────┘                      │
│                                           │                              │
│                                           v                              │
│                                    ┌─────────────┐                       │
│                                    │  Database   │                       │
│                                    └─────────────┘                       │
└──────────────────────────────────────────────────────────────────────────┘


# === BACKEND: API Flask Simple ===

# app.py (Flask Backend)
from flask import Flask, jsonify, request
from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # Permet requêtes depuis React (localhost:3000)

# Base de données simple (en vrai: PostgreSQL, MySQL, etc.)
users = [
    {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'},
    {'id': 2, 'name': 'Bob', 'email': 'bob@example.com'}
]

# GET: Récupérer tous les utilisateurs
@app.route('/api/users', methods=['GET'])
def get_users():
    return jsonify(users)

# GET: Récupérer un utilisateur par ID
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = next((u for u in users if u['id'] == user_id), None)
    if user:
        return jsonify(user)
    return jsonify({'error': 'User not found'}), 404

# POST: Créer un nouvel utilisateur
@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    new_user = {
        'id': len(users) + 1,
        'name': data['name'],
        'email': data['email']
    }
    users.append(new_user)
    return jsonify(new_user), 201

# PUT: Mettre à jour un utilisateur
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    user = next((u for u in users if u['id'] == user_id), None)
    if not user:
        return jsonify({'error': 'User not found'}), 404
    
    data = request.get_json()
    user['name'] = data.get('name', user['name'])
    user['email'] = data.get('email', user['email'])
    return jsonify(user)

# DELETE: Supprimer un utilisateur
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    global users
    users = [u for u in users if u['id'] != user_id]
    return jsonify({'message': 'User deleted'}), 200

if __name__ == '__main__':
    app.run(debug=True, port=5000)

# Lancer: python app.py
# API disponible sur http://localhost:5000


# === CONFIGURATION CORS (Importante!) ===

# CORS = Cross-Origin Resource Sharing
# Permet à React (localhost:3000) de faire des requêtes à Flask (localhost:5000)

# Installation
pip install flask-cors

# Configuration basique
from flask_cors import CORS
CORS(app)  # Autorise toutes les origines (dev seulement!)

# Configuration production (plus sûre)
CORS(app, resources={
    r"/api/*": {
        "origins": ["https://monapp.com"],
        "methods": ["GET", "POST", "PUT", "DELETE"],
        "allow_headers": ["Content-Type", "Authorization"]
    }
})


# === FRONTEND: Consommation API avec React ===

# Méthode 1: fetch() natif (Built-in JavaScript)


# === useState - Gestion d'état basique ===

import { useState } from 'react';

// État simple
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

// État avec valeur initiale complexe
const [user, setUser] = useState({
  name: 'John',
  age: 30,
  email: 'john@example.com'
});

// État avec lazy initialization (pour calculs coûteux)
const [data, setData] = useState(() => {
  const initialData = computeExpensiveValue();
  return initialData;
});

// Mise à jour avec fonction (accès à valeur précédente)
const [count, setCount] = useState(0);

// [X] Mauvais (peut être incorrect avec batching)
setCount(count + 1);
setCount(count + 1); // Ne donnera pas count + 2

// [OK] Bon (toujours correct)
setCount(prev => prev + 1);
setCount(prev => prev + 1); // Donnera count + 2

// Mise à jour d'objet (immutabilité)
const [user, setUser] = useState({ name: 'John', age: 30 });

// [X] Mauvais (mutation)
user.age = 31;
setUser(user);

// [OK] Bon (spread operator)
setUser({ ...user, age: 31 });

// Mise à jour d'un champ spécifique
setUser(prev => ({ ...prev, age: 31 }));

// Mise à jour tableau
const [items, setItems] = useState([1, 2, 3]);

// Ajouter
setItems([...items, 4]);
setItems(prev => [...prev, 4]);

// Supprimer par index
setItems(items.filter((_, index) => index !== indexToRemove));

// Supprimer par valeur
setItems(items.filter(item => item.id !== idToRemove));

// Modifier un élément
setItems(items.map(item => 
  item.id === targetId ? { ...item, completed: true } : item
));

// Réinitialiser état
setCount(0);
setUser({ name: '', age: 0 });
setItems([]);

// Multiples états
function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);
  
  return (
    <form>
      <input value={name} onChange={e => setName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <input value={age} onChange={e => setAge(Number(e.target.value))} />
    </form>
  );
}

// État groupé vs états multiples
// [OK] Bon pour données liées
const [form, setForm] = useState({ name: '', email: '', age: 0 });

// [OK] Bon pour données indépendantes
const [name, setName] = useState('');
const [isOpen, setIsOpen] = useState(false);


[OK] HOOKS - useEffect


# === useEffect - Side Effects ===

import { useEffect } from 'react';

// Effect de base (s'exécute après chaque render)
useEffect(() => {
  console.log('Component rendered');
});

// Effect avec cleanup
useEffect(() => {
  console.log('Component mounted');
  
  return () => {
    console.log('Component unmounted');
  };
});

// Effect avec dépendances (s'exécute quand les dépendances changent)
useEffect(() => {
  console.log('Count changed:', count);
}, [count]);

// Effect qui s'exécute une seule fois au mount (dépendances vides)
useEffect(() => {
  console.log('Component mounted once');
}, []);

// Fetching data
useEffect(() => {
  async function fetchData() {
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();
    setUser(data);
  }
  
  fetchData();
}, [userId]);

// Avec cleanup pour éviter memory leaks
useEffect(() => {
  let cancelled = false;
  
  async function fetchData() {
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();
    
    if (!cancelled) {
      setUser(data);
    }
  }
  
  fetchData();
  
  return () => {
    cancelled = true;
  };
}, [userId]);

// Abonnement avec cleanup
useEffect(() => {
  const subscription = someAPI.subscribe(data => {
    setData(data);
  });
  
  return () => {
    subscription.unsubscribe();
  };
}, []);

// Timer avec cleanup
useEffect(() => {
  const timer = setTimeout(() => {
    console.log('Delayed action');
  }, 1000);
  
  return () => {
    clearTimeout(timer);
  };
}, []);

// Interval avec cleanup
useEffect(() => {
  const interval = setInterval(() => {
    setCount(prev => prev + 1);
  }, 1000);
  
  return () => {
    clearInterval(interval);
  };
}, []);

// Event listeners avec cleanup
useEffect(() => {
  function handleResize() {
    setWindowWidth(window.innerWidth);
  }
  
  window.addEventListener('resize', handleResize);
  
  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []);

// Effect multiple pour séparation des préoccupations
useEffect(() => {
  // Logique 1
}, [dep1]);

useEffect(() => {
  // Logique 2
}, [dep2]);

// IMPORTANT: Dépendances exhaustives
// [OK] Bon
useEffect(() => {
  console.log(count, user);
}, [count, user]);

// [X] Mauvais (dépendances manquantes)
useEffect(() => {
  console.log(count, user);
}, [count]); // user manquant!

// Ignorer lint warning (à éviter sauf cas spécifique)
useEffect(() => {
  // Logique
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);


[OK] HOOKS - useContext


# === useContext - Partage d'état global ===

import { createContext, useContext, useState } from 'react';

// Créer un contexte
const ThemeContext = createContext('light');

// Provider
function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={theme}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// Consommer avec useContext
function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Click me</button>;
}

// Contexte avec valeur et setter
const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  const value = { theme, toggleTheme };
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

// Hook personnalisé pour contexte
function useTheme() {
  const context = useContext(ThemeContext);
  
  if (context === undefined) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  
  return context;
}

// Utilisation
function ThemedButton() {
  const { theme, toggleTheme } = useTheme();
  
  return (
    <button onClick={toggleTheme} className={theme}>
      Current theme: {theme}
    </button>
  );
}

// Contexte avec valeur par défaut
const UserContext = createContext({
  user: null,
  setUser: () => {}
});

// Multiples contextes
function App() {
  return (
    <ThemeProvider>
      <UserProvider>
        <LanguageProvider>
          <MainApp />
        </LanguageProvider>
      </UserProvider>
    </ThemeProvider>
  );
}

// Optimisation: Séparer contextes pour éviter re-renders
// [X] Mauvais (tout re-render si theme OU user change)
const AppContext = createContext();
const value = { theme, user, toggleTheme, setUser };

// [OK] Bon (re-renders séparés)
<ThemeContext.Provider value={{ theme, toggleTheme }}>
  <UserContext.Provider value={{ user, setUser }}>
    {children}
  </UserContext.Provider>
</ThemeContext.Provider>

// Avec TypeScript
interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);


[OK] HOOKS - useReducer


# === useReducer - Gestion d'état complexe ===

import { useReducer } from 'react';

// Définir reducer
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}

// Utilisation
function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

// Avec payload
function todoReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...state, { id: Date.now(), text: action.payload, completed: false }];
    case 'toggle':
      return state.map(todo =>
        todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
      );
    case 'delete':
      return state.filter(todo => todo.id !== action.payload);
    default:
      return state;
  }
}

function TodoList() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  
  const addTodo = (text) => {
    dispatch({ type: 'add', payload: text });
  };
  
  const toggleTodo = (id) => {
    dispatch({ type: 'toggle', payload: id });
  };
  
  const deleteTodo = (id) => {
    dispatch({ type: 'delete', payload: id });
  };
  
  return <div>{/* UI */}</div>;
}

// Init state avec fonction (lazy initialization)
function init(initialCount) {
  return { count: initialCount };
}

function Counter({ initialCount }) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  // ...
}

// Reducer complexe avec état imbriqué
function formReducer(state, action) {
  switch (action.type) {
    case 'update_field':
      return {
        ...state,
        fields: {
          ...state.fields,
          [action.field]: action.value
        }
      };
    case 'add_error':
      return {
        ...state,
        errors: [...state.errors, action.error]
      };
    case 'clear_errors':
      return {
        ...state,
        errors: []
      };
    case 'submit':
      return {
        ...state,
        isSubmitting: true
      };
    case 'submit_success':
      return {
        ...state,
        isSubmitting: false,
        submitted: true
      };
    default:
      return state;
  }
}

// useReducer vs useState
// [OK] useReducer quand:
//   - État complexe avec logique de mise à jour
//   - Multiples sous-valeurs
//   - Prochain état dépend du précédent
//   - Logique testable séparément

// [OK] useState quand:
//   - État simple
//   - Peu de transitions d'état
//   - Pas de logique complexe

// Avec TypeScript
type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'set'; payload: number };

interface State {
  count: number;
}

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'set':
      return { count: action.payload };
  }
}


[OK] HOOKS - useRef


# === useRef - Références mutables ===

import { useRef } from 'react';

// Référence DOM
function TextInput() {
  const inputRef = useRef(null);
  
  const focusInput = () => {
    inputRef.current.focus();
  };
  
  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

// Valeur mutable qui persiste entre renders (sans re-render)
function Timer() {
  const intervalRef = useRef(null);
  const [count, setCount] = useState(0);
  
  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
  };
  
  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };
  
  useEffect(() => {
    return () => clearInterval(intervalRef.current);
  }, []);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

// Stocker valeur précédente
function usePrevious(value) {
  const ref = useRef();
  
  useEffect(() => {
    ref.current = value;
  }, [value]);
  
  return ref.current;
}

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  
  return (
    <div>
      <p>Current: {count}</p>
      <p>Previous: {prevCount}</p>
    </div>
  );
}

// Éviter re-render inutile
// [OK] useRef ne cause pas de re-render
const renderCountRef = useRef(0);
renderCountRef.current++;

// [X] useState cause re-render
const [renderCount, setRenderCount] = useState(0);
setRenderCount(prev => prev + 1); // Re-render!

// Callback ref (pour mesures DOM)
function MeasureDiv() {
  const [height, setHeight] = useState(0);
  
  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);
  
  return <div ref={measuredRef}>Height: {height}</div>;
}

// Accéder à plusieurs refs
function Form() {
  const inputRefs = useRef([]);
  
  const focusInput = (index) => {
    inputRefs.current[index]?.focus();
  };
  
  return (
    <form>
      <input ref={el => inputRefs.current[0] = el} />
      <input ref={el => inputRefs.current[1] = el} />
      <input ref={el => inputRefs.current[2] = el} />
    </form>
  );
}

// Forward ref (passer ref aux enfants)
import { forwardRef } from 'react';

const CustomInput = forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});

function Parent() {
  const inputRef = useRef(null);
  
  return <CustomInput ref={inputRef} />;
}

// useRef vs useState
// useRef:
//   - Pas de re-render quand la valeur change
//   - Mutable (ref.current = newValue)
//   - Persiste entre renders
//   - Pour références DOM, timers, valeurs précédentes

// useState:
//   - Re-render quand la valeur change
//   - Immutable (setCount(newValue))
//   - Persiste entre renders
//   - Pour données qui affectent l'UI


[OK] HOOKS - useMemo et useCallback


# === useMemo - Mémoïsation de valeurs ===

import { useMemo } from 'react';

// Calcul coûteux
function ExpensiveComponent({ items }) {
  const expensiveResult = useMemo(() => {
    console.log('Computing expensive value...');
    return items.reduce((acc, item) => acc + item.value, 0);
  }, [items]);
  
  return <div>Total: {expensiveResult}</div>;
}

// Création d'objet (éviter nouvelle référence à chaque render)
function Parent() {
  const [count, setCount] = useState(0);
  
  // [X] Mauvais: nouvel objet à chaque render
  const config = { theme: 'dark', lang: 'fr' };
  
  // [OK] Bon: même objet si dépendances inchangées
  const config = useMemo(() => ({ theme: 'dark', lang: 'fr' }), []);
  
  return <Child config={config} />;
}

// Filtrage/tri de liste
function TodoList({ todos, filter }) {
  const filteredTodos = useMemo(() => {
    console.log('Filtering todos...');
    return todos.filter(todo => {
      if (filter === 'completed') return todo.completed;
      if (filter === 'active') return !todo.completed;
      return true;
    });
  }, [todos, filter]);
  
  return (
    <ul>
      {filteredTodos.map(todo => <TodoItem key={todo.id} todo={todo} />)}
    </ul>
  );
}

// Recherche/filtre complexe
function SearchResults({ items, query }) {
  const results = useMemo(() => {
    const lowercaseQuery = query.toLowerCase();
    return items.filter(item =>
      item.name.toLowerCase().includes(lowercaseQuery) ||
      item.description.toLowerCase().includes(lowercaseQuery)
    );
  }, [items, query]);
  
  return <ResultsList results={results} />;
}


# === useCallback - Mémoïsation de fonctions ===

import { useCallback } from 'react';

// Fonction stable pour éviter re-render enfant
function Parent() {
  const [count, setCount] = useState(0);
  
  // [X] Mauvais: nouvelle fonction à chaque render
  const handleClick = () => {
    console.log('Clicked');
  };
  
  // [OK] Bon: même fonction si dépendances inchangées
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []);
  
  return <MemoizedChild onClick={handleClick} />;
}

// Avec dépendances
function TodoList() {
  const [todos, setTodos] = useState([]);
  
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  }, []); // Pas de dépendances car on utilise fonction dans setState
  
  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, []);
  
  return (
    <div>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={handleToggle}
          onDelete={handleDelete}
        />
      ))}
    </div>
  );
}

// Avec useEffect dependencies
function SearchComponent({ query }) {
  const [results, setResults] = useState([]);
  
  const fetchResults = useCallback(async () => {
    const response = await fetch(`/api/search?q=${query}`);
    const data = await response.json();
    setResults(data);
  }, [query]);
  
  useEffect(() => {
    fetchResults();
  }, [fetchResults]); // fetchResults stable grâce à useCallback
  
  return <div>{/* ... */}</div>;
}

// Optimisation avec React.memo
const TodoItem = React.memo(({ todo, onToggle, onDelete }) => {
  console.log('TodoItem render:', todo.id);
  
  return (
    <div>
      <span>{todo.text}</span>
      <button onClick={() => onToggle(todo.id)}>Toggle</button>
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </div>
  );
});

// useMemo vs useCallback
// useMemo: mémoïse le RÉSULTAT d'une fonction
const value = useMemo(() => computeExpensiveValue(a, b), [a, b]);

// useCallback: mémoïse la FONCTION elle-même
const callback = useCallback(() => doSomething(a, b), [a, b]);

// Équivalence:
useCallback(fn, deps) === useMemo(() => fn, deps)

// Quand utiliser?
// [OK] useMemo:
//   - Calculs coûteux
//   - Création d'objets/tableaux pour props
//   - Transformation de données

// [OK] useCallback:
//   - Fonctions passées à composants mémoïsés
//   - Dépendances de useEffect
//   - Event handlers dans listes

// [X] Ne pas utiliser si:
//   - Calcul rapide
//   - Composant ne re-render pas souvent
//   - Optimisation prématurée


[OK] HOOKS - Autres Hooks Intégrés


# === useLayoutEffect - Mesures DOM avant paint ===

import { useLayoutEffect } from 'react';

// Utiliser pour mesures/manipulations DOM avant affichage
function Tooltip({ children }) {
  const [tooltipHeight, setTooltipHeight] = useState(0);
  const tooltipRef = useRef(null);
  
  useLayoutEffect(() => {
    // S'exécute AVANT que le navigateur ne peint
    const { height } = tooltipRef.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, []);
  
  return (
    <div ref={tooltipRef} style={{ marginTop: -tooltipHeight }}>
      {children}
    </div>
  );
}

// useEffect vs useLayoutEffect:
// useEffect: après paint (asynchrone) - 99% des cas
// useLayoutEffect: avant paint (synchrone) - mesures DOM, animations


# === useImperativeHandle - Personnaliser ref exposée ===

import { forwardRef, useImperativeHandle, useRef } from 'react';

const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);
  
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    scrollIntoView: () => {
      inputRef.current.scrollIntoView();
    },
    getValue: () => {
      return inputRef.current.value;
    }
  }));
  
  return <input ref={inputRef} {...props} />;
});

// Utilisation
function Parent() {
  const inputRef = useRef(null);
  
  const handleFocus = () => {
    inputRef.current.focus();
    console.log('Value:', inputRef.current.getValue());
  };
  
  return (
    <>
      <CustomInput ref={inputRef} />
      <button onClick={handleFocus}>Focus Input</button>
    </>
  );
}


# === useDebugValue - Debug custom hooks ===

import { useDebugValue } from 'react';

function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);
  
  // Affiche dans React DevTools
  useDebugValue(isOnline ? 'Online' : 'Offline');
  
  useEffect(() => {
    // Logique...
  }, []);
  
  return isOnline;
}

// Avec formatage lazy (pour calculs coûteux)
function useDate() {
  const [date, setDate] = useState(new Date());
  
  useDebugValue(date, date => date.toISOString());
  
  return date;
}


# === useId - Génération d'IDs uniques (React 18+) ===

import { useId } from 'react';

function NameField() {
  const id = useId();
  
  return (
    <div>
      <label htmlFor={id}>Name:</label>
      <input id={id} type="text" />
    </div>
  );
}

// Pour plusieurs IDs liés
function FormField() {
  const id = useId();
  
  return (
    <div>
      <label htmlFor={id + '-name'}>Name:</label>
      <input id={id + '-name'} type="text" />
      
      <label htmlFor={id + '-email'}>Email:</label>
      <input id={id + '-email'} type="email" />
    </div>
  );
}


# === useDeferredValue - Différer mise à jour (React 18+) ===

import { useDeferredValue } from 'react';

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  
  // Re-render immédiat avec query
  // Re-render différé avec deferredQuery (pour calculs lourds)
  const results = useMemo(() => {
    return searchInLargeList(deferredQuery);
  }, [deferredQuery]);
  
  return (
    <div>
      {/* Input réactif */}
      <input value={query} />
      
      {/* Résultats différés */}
      <ResultsList results={results} />
    </div>
  );
}


# === useTransition - Transitions non-bloquantes (React 18+) ===

import { useTransition } from 'react';

function SearchBox() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();
  
  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // Mise à jour urgente (immédiate)
    
    startTransition(() => {
      // Mise à jour non-urgente (peut être interrompue)
      const filtered = expensiveFilter(items, value);
      setResults(filtered);
    });
  };
  
  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <ResultsList results={results} />
    </div>
  );
}


# === useSyncExternalStore - Souscription à store externe ===

import { useSyncExternalStore } from 'react';

// Pour bibliothèques externes (Redux, Zustand, etc.)
function useStore(store) {
  return useSyncExternalStore(
    store.subscribe,           // Fonction subscribe
    store.getSnapshot,         // Obtenir état actuel
    store.getServerSnapshot    // Optionnel: SSR
  );
}

// Exemple avec navigator.onLine
function useOnlineStatus() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('online', callback);
      window.addEventListener('offline', callback);
      return () => {
        window.removeEventListener('online', callback);
        window.removeEventListener('offline', callback);
      };
    },
    () => navigator.onLine,
    () => true // Serveur assume toujours online
  );
}


[OK] HOOKS PERSONNALISÉS


# === Créer des hooks personnalisés ===

// Hook simple
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  
  return width;
}

// Utilisation
function MyComponent() {
  const width = useWindowWidth();
  return <div>Window width: {width}</div>;
}


// Hook avec paramètres
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => clearTimeout(timer);
  }, [value, delay]);
  
  return debouncedValue;
}

// Utilisation
function SearchBox() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);
  
  useEffect(() => {
    if (debouncedQuery) {
      fetchResults(debouncedQuery);
    }
  }, [debouncedQuery]);
  
  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}


// Hook fetch data
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let cancelled = false;
    
    async function fetchData() {
      try {
        setLoading(true);
        const response = await fetch(url);
        const json = await response.json();
        
        if (!cancelled) {
          setData(json);
          setError(null);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }
    
    fetchData();
    
    return () => {
      cancelled = true;
    };
  }, [url]);
  
  return { data, loading, error };
}

// Utilisation
function UserProfile({ userId }) {
  const { data, loading, error } = useFetch(`/api/users/${userId}`);
  
  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  return <div>{data.name}</div>;
}


// Hook localStorage
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });
  
  const setValue = (value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
}

// Utilisation
function Settings() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  return <button onClick={() => setTheme('dark')}>Change Theme</button>;
}


// Hook toggle
function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  
  const toggle = useCallback(() => {
    setValue(prev => !prev);
  }, []);
  
  return [value, toggle];
}

// Utilisation
function Modal() {
  const [isOpen, toggleOpen] = useToggle(false);
  
  return (
    <>
      <button onClick={toggleOpen}>Open Modal</button>
      {isOpen && <ModalContent onClose={toggleOpen} />}
    </>
  );
}


// Hook async avec AbortController
function useAsync(asyncFunction, immediate = true) {
  const [status, setStatus] = useState('idle');
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  
  const execute = useCallback(async (...params) => {
    setStatus('pending');
    setData(null);
    setError(null);
    
    try {
      const response = await asyncFunction(...params);
      setData(response);
      setStatus('success');
      return response;
    } catch (err) {
      setError(err);
      setStatus('error');
      throw err;
    }
  }, [asyncFunction]);
  
  useEffect(() => {
    if (immediate) {
      execute();
    }
  }, [execute, immediate]);
  
  return { execute, status, data, error };
}


// Hook interval
function useInterval(callback, delay) {
  const savedCallback = useRef();
  
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);
  
  useEffect(() => {
    if (delay !== null) {
      const id = setInterval(() => savedCallback.current(), delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

// Utilisation
function Timer() {
  const [count, setCount] = useState(0);
  
  useInterval(() => {
    setCount(count + 1);
  }, 1000);
  
  return <div>{count}</div>;
}


// Hook événements clavier
function useKeyPress(targetKey) {
  const [keyPressed, setKeyPressed] = useState(false);
  
  useEffect(() => {
    const downHandler = ({ key }) => {
      if (key === targetKey) {
        setKeyPressed(true);
      }
    };
    
    const upHandler = ({ key }) => {
      if (key === targetKey) {
        setKeyPressed(false);
      }
    };
    
    window.addEventListener('keydown', downHandler);
    window.addEventListener('keyup', upHandler);
    
    return () => {
      window.removeEventListener('keydown', downHandler);
      window.removeEventListener('keyup', upHandler);
    };
  }, [targetKey]);
  
  return keyPressed;
}

// Utilisation
function Game() {
  const upPressed = useKeyPress('ArrowUp');
  const downPressed = useKeyPress('ArrowDown');
  
  return (
    <div>
      <div>Up: {upPressed ? '[ROUGE]' : '[BLANC]'}</div>
      <div>Down: {downPressed ? '[ROUGE]' : '[BLANC]'}</div>
    </div>
  );
}


[OK] PERFORMANCE - OPTIMISATION


# === React.memo - Mémoïser composants ===

import { memo } from 'react';

// Composant sans memo (re-render à chaque fois)
function TodoItem({ todo, onToggle }) {
  console.log('Render:', todo.id);
  return <div onClick={() => onToggle(todo.id)}>{todo.text}</div>;
}

// Avec memo (re-render seulement si props changent)
const TodoItem = memo(function TodoItem({ todo, onToggle }) {
  console.log('Render:', todo.id);
  return <div onClick={() => onToggle(todo.id)}>{todo.text}</div>;
});

// Avec comparaison personnalisée
const TodoItem = memo(
  function TodoItem({ todo, onToggle }) {
    return <div onClick={() => onToggle(todo.id)}>{todo.text}</div>;
  },
  (prevProps, nextProps) => {
    // Retourner true si égaux (pas de re-render)
    return prevProps.todo.id === nextProps.todo.id &&
           prevProps.todo.text === nextProps.todo.text &&
           prevProps.todo.completed === nextProps.todo.completed;
  }
);


# === Code splitting avec lazy et Suspense ===

import { lazy, Suspense } from 'react';

// Chargement lazy d'un composant
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <HeavyComponent />
    </Suspense>
  );
}

// Avec routes
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));

function App() {
  return (
    <Router>
      <Suspense fallback={<Spinner />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

// Chargement avec délai minimum (éviter flash)
const HeavyComponent = lazy(() => 
  Promise.all([
    import('./HeavyComponent'),
    new Promise(resolve => setTimeout(resolve, 300))
  ]).then(([module]) => module)
);

// Préchargement
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  const handleMouseEnter = () => {
    // Précharger avant le clic
    import('./HeavyComponent');
  };
  
  return (
    <button onMouseEnter={handleMouseEnter}>
      Load Heavy Component
    </button>
  );
}


# === Virtualization - Longues listes ===

// Avec react-window
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );
  
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}


# === Debouncing et Throttling ===

// Debounce custom hook
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => clearTimeout(handler);
  }, [value, delay]);
  
  return debouncedValue;
}

// Throttle avec lodash
import { throttle } from 'lodash';

function ScrollComponent() {
  const handleScroll = useMemo(
    () => throttle(() => {
      console.log('Scrolling...');
    }, 200),
    []
  );
  
  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);
  
  return <div>{/* ... */}</div>;
}


# === Éviter re-renders inutiles ===

// [X] Mauvais: nouveau objet à chaque render
function Parent() {
  const style = { color: 'red' };
  return <Child style={style} />;
}

// [OK] Bon: objet stable
function Parent() {
  const style = useMemo(() => ({ color: 'red' }), []);
  return <Child style={style} />;
}

// [X] Mauvais: nouvelle fonction à chaque render
function Parent() {
  return <Child onClick={() => console.log('click')} />;
}

// [OK] Bon: fonction stable
function Parent() {
  const handleClick = useCallback(() => {
    console.log('click');
  }, []);
  return <Child onClick={handleClick} />;
}

// [X] Mauvais: calcul dans render
function Component({ items }) {
  const filtered = items.filter(item => item.active); // À chaque render!
  return <List items={filtered} />;
}

// [OK] Bon: avec useMemo
function Component({ items }) {
  const filtered = useMemo(
    () => items.filter(item => item.active),
    [items]
  );
  return <List items={filtered} />;
}


# === Profiling et mesures ===

import { Profiler } from 'react';

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id} took ${actualDuration}ms`);
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MainContent />
    </Profiler>
  );
}

// React DevTools Profiler:
// - Enregistrer session
// - Identifier composants lents
// - Voir re-renders inutiles


[OK] PATTERNS AVANCÉS


# === Render Props ===

// Pattern: Passer fonction comme children
function DataProvider({ render, url }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);
  
  return render({ data, loading });
}

// Utilisation
function App() {
  return (
    <DataProvider
      url="/api/users"
      render={({ data, loading }) => (
        loading ? <Spinner /> : <UserList users={data} />
      )}
    />
  );
}

// Variante avec children
function DataProvider({ children, url }) {
  // ... même logique
  return children({ data, loading });
}

<DataProvider url="/api/users">
  {({ data, loading }) => (
    loading ? <Spinner /> : <UserList users={data} />
  )}
</DataProvider>


# === Higher-Order Components (HOC) ===

// HOC pour ajouter fonctionnalité
function withLoading(Component) {
  return function WithLoadingComponent({ isLoading, ...props }) {
    if (isLoading) return <Spinner />;
    return <Component {...props} />;
  };
}

// Utilisation
const UserListWithLoading = withLoading(UserList);

function App() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  
  return <UserListWithLoading users={users} isLoading={loading} />;
}

// HOC avec paramètres
function withAuth(Component, requiredRole) {
  return function WithAuthComponent(props) {
    const { user } = useAuth();
    
    if (!user) return <Redirect to="/login" />;
    if (requiredRole && user.role !== requiredRole) {
      return <Unauthorized />;
    }
    
    return <Component {...props} />;
  };
}

const AdminPanel = withAuth(AdminPanelComponent, 'admin');

// Composer plusieurs HOCs
import { compose } from 'redux';

const enhance = compose(
  withAuth,
  withLoading,
  withErrorBoundary
);

const EnhancedComponent = enhance(MyComponent);


# === Compound Components ===

// Pattern: Composants liés partagent état implicite
const TabsContext = createContext();

function Tabs({ children, defaultValue }) {
  const [activeTab, setActiveTab] = useState(defaultValue);
  
  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabList({ children }) {
  return <div className="tab-list">{children}</div>;
}

function Tab({ value, children }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  const isActive = activeTab === value;
  
  return (
    <button
      className={isActive ? 'tab active' : 'tab'}
      onClick={() => setActiveTab(value)}
    >
      {children}
    </button>
  );
}

function TabPanel({ value, children }) {
  const { activeTab } = useContext(TabsContext);
  if (activeTab !== value) return null;
  return <div className="tab-panel">{children}</div>;
}

// API publique
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;

// Utilisation
function App() {
  return (
    <Tabs defaultValue="tab1">
      <Tabs.List>
        <Tabs.Tab value="tab1">Tab 1</Tabs.Tab>
        <Tabs.Tab value="tab2">Tab 2</Tabs.Tab>
      </Tabs.List>
      
      <Tabs.Panel value="tab1">Content 1</Tabs.Panel>
      <Tabs.Panel value="tab2">Content 2</Tabs.Panel>
    </Tabs>
  );
}


# === Control Props Pattern ===

// Laisser parent contrôler état
function SearchInput({ value, onChange, ...props }) {
  // Composant contrôlé
  return (
    <input
      type="text"
      value={value}
      onChange={e => onChange(e.target.value)}
      {...props}
    />
  );
}

// Composant avec contrôle optionnel
function Tabs({ value: controlledValue, onChange, defaultValue, children }) {
  const [internalValue, setInternalValue] = useState(defaultValue);
  
  const isControlled = controlledValue !== undefined;
  const value = isControlled ? controlledValue : internalValue;
  
  const handleChange = (newValue) => {
    if (!isControlled) {
      setInternalValue(newValue);
    }
    onChange?.(newValue);
  };
  
  return (
    <TabsContext.Provider value={{ value, onChange: handleChange }}>
      {children}
    </TabsContext.Provider>
  );
}

// Utilisation: contrôlé
function App() {
  const [tab, setTab] = useState('tab1');
  return <Tabs value={tab} onChange={setTab}>{/* ... */}</Tabs>;
}

// Utilisation: non-contrôlé
function App() {
  return <Tabs defaultValue="tab1">{/* ... */}</Tabs>;
}


# === State Reducer Pattern ===

// Permettre utilisateur personnaliser logique état
function useToggle({ reducer = toggleReducer } = {}) {
  const [{ on }, dispatch] = useReducer(reducer, { on: false });
  
  const toggle = () => dispatch({ type: 'toggle' });
  const setOn = () => dispatch({ type: 'on' });
  const setOff = () => dispatch({ type: 'off' });
  
  return { on, toggle, setOn, setOff };
}

function toggleReducer(state, action) {
  switch (action.type) {
    case 'toggle':
      return { on: !state.on };
    case 'on':
      return { on: true };
    case 'off':
      return { on: false };
    default:
      throw new Error(`Unsupported type: ${action.type}`);
  }
}

// Utilisateur peut personnaliser
function customReducer(state, action) {
  // Logique personnalisée
  if (action.type === 'toggle' && state.toggleCount >= 4) {
    return state; // Bloquer après 4 toggles
  }
  
  // Fallback vers comportement par défaut
  return toggleReducer(state, action);
}

const { on, toggle } = useToggle({ reducer: customReducer });


[OK] ERROR BOUNDARIES


# === Gestion d'erreurs ===

import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('Error caught:', error, errorInfo);
    // Log vers service (Sentry, etc.)
    this.setState({ error, errorInfo });
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h1>Something went wrong.</h1>
          <details>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo?.componentStack}
          </details>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Utilisation
function App() {
  return (
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
  );
}

// Error boundary avec reset
class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  
  resetError = () => {
    this.setState({ hasError: false });
  };
  
  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h1>Error occurred</h1>
          <button onClick={this.resetError}>Try again</button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Multiples error boundaries
function App() {
  return (
    <ErrorBoundary fallback={<GlobalError />}>
      <Header />
      
      <ErrorBoundary fallback={<SidebarError />}>
        <Sidebar />
      </ErrorBoundary>
      
      <ErrorBoundary fallback={<ContentError />}>
        <MainContent />
      </ErrorBoundary>
    </ErrorBoundary>
  );
}


[OK] PORTALS


# === Rendre en dehors de la hiérarchie DOM ===

import { createPortal } from 'react-dom';

function Modal({ children, isOpen }) {
  if (!isOpen) return null;
  
  return createPortal(
    <div className="modal-overlay">
      <div className="modal">{children}</div>
    </div>,
    document.body
  );
}

// Utilisation
function App() {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <div>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>
      
      <Modal isOpen={isOpen}>
        <h2>Modal Title</h2>
        <button onClick={() => setIsOpen(false)}>Close</button>
      </Modal>
    </div>
  );
}

// Portal vers élément spécifique
function Tooltip({ children, target }) {
  return createPortal(
    <div className="tooltip">{children}</div>,
    document.querySelector(target)
  );
}

// Portal avec événements (bubbling fonctionne)
function Parent() {
  const handleClick = () => {
    console.log('Clicked!'); // Marche même si dans portal!
  };
  
  return (
    <div onClick={handleClick}>
      <Modal>
        <button>Click me</button>
      </Modal>
    </div>
  );
}


[OK] REFS - AVANCÉ


# === Forward Ref ===

import { forwardRef } from 'react';

const FancyButton = forwardRef((props, ref) => (
  <button ref={ref} className="fancy-button">
    {props.children}
  </button>
));

// Utilisation
function Parent() {
  const btnRef = useRef(null);
  
  useEffect(() => {
    btnRef.current.focus();
  }, []);
  
  return <FancyButton ref={btnRef}>Click me</FancyButton>;
}

// Avec TypeScript
interface ButtonProps {
  children: React.ReactNode;
  onClick?: () => void;
}

const FancyButton = forwardRef<HTMLButtonElement, ButtonProps>(
  (props, ref) => (
    <button ref={ref} {...props}>
      {props.children}
    </button>
  )
);


# === Callback Refs ===

function MeasureComponent() {
  const [height, setHeight] = useState(0);
  
  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);
  
  return (
    <div ref={measuredRef}>
      <p>Height: {height}px</p>
    </div>
  );
}


# === Refs avec liste dynamique ===

function ItemList({ items }) {
  const itemsRef = useRef(new Map());
  
  const getItemRef = (id) => (node) => {
    if (node) {
      itemsRef.current.set(id, node);
    } else {
      itemsRef.current.delete(id);
    }
  };
  
  const scrollToItem = (id) => {
    const node = itemsRef.current.get(id);
    node?.scrollIntoView({ behavior: 'smooth' });
  };
  
  return (
    <div>
      {items.map(item => (
        <div key={item.id} ref={getItemRef(item.id)}>
          {item.name}
        </div>
      ))}
    </div>
  );
}


[OK] CONCURRENT FEATURES (React 18+)


# === startTransition ===

import { startTransition } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  const handleChange = (e) => {
    const value = e.target.value;
    
    // Mise à jour urgente (synchrone)
    setQuery(value);
    
    // Mise à jour non-urgente (peut être interrompue)
    startTransition(() => {
      const filtered = heavyFilter(items, value);
      setResults(filtered);
    });
  };
  
  return (
    <>
      <input value={query} onChange={handleChange} />
      <ResultsList results={results} />
    </>
  );
}


# === Suspense pour Data Fetching ===

// Avec React 18+ et frameworks supportant Suspense
const resource = fetchData(); // Retourne une "resource"

function UserProfile() {
  const user = resource.read(); // Suspend si pas prêt
  
  return <div>{user.name}</div>;
}

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <UserProfile />
    </Suspense>
  );
}

// Suspense imbriqué
function App() {
  return (
    <Suspense fallback={<PageSpinner />}>
      <Header />
      
      <Suspense fallback={<SidebarSkeleton />}>
        <Sidebar />
      </Suspense>
      
      <Suspense fallback={<ContentSkeleton />}>
        <MainContent />
      </Suspense>
    </Suspense>
  );
}


# Fichier: python_cheats/cheatsheets/react-advanced.txt
# Cheatsheet React.js - Guide Avancé et Fonctionnalités Modernes


[OK] REACT SERVER COMPONENTS (RSC) - React 18+


Les React Server Components sont des composants qui s'exécutent UNIQUEMENT côté
serveur. Ils ne sont jamais envoyés au client, réduisant drastiquement la taille
du bundle JavaScript.

# === Concepts fondamentaux ===

DIFFÉRENCES Server vs Client Components:

┌──────────────────────────────────────────────────────────────────┐
│  SERVER COMPONENTS              │  CLIENT COMPONENTS             │
├─────────────────────────────────┼────────────────────────────────┤
│  • S'exécutent sur le serveur   │  • S'exécutent dans navigateur │
│  • Pas de JavaScript au client  │  • Envoyés au client          │
│  • Accès direct DB, filesystem  │  • Pas d'accès serveur        │
│  • Pas de hooks useState, etc.  │  • Tous les hooks disponibles │
│  • Pas d'event handlers         │  • Event handlers OK          │
│  • Peuvent être async           │  • Pas async (sauf useEffect) │
│  • Par défaut dans Next.js 13+  │  • Marqués avec 'use client'  │
└──────────────────────────────────────────────────────────────────┘


# === Server Component basique ===

// app/users/page.tsx (Next.js 13+)
// Par défaut, c'est un Server Component

async function UsersPage() {
  // [OK] Fetch direct côté serveur (pas de useEffect!)
  const users = await db.users.findMany();
  
  // [OK] Accès variables d'environnement privées
  const apiKey = process.env.SECRET_API_KEY;
  
  // [OK] Import de gros packages sans impact sur le client
  const bcrypt = await import('bcrypt');
  
  return (
    <div>
      <h1>Users</h1>
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

// AVANTAGES:
// 1. Aucun JavaScript envoyé au client pour ce composant
// 2. Pas de waterfall requests (tout fetch côté serveur)
// 3. Sécurité: code sensible reste sur le serveur
// 4. Performance: rendering initial plus rapide


# === Client Component ===

// components/Counter.tsx
'use client'; // <- Directive obligatoire en haut du fichier

import { useState } from 'react';

export function Counter() {
  // [OK] Hooks disponibles
  const [count, setCount] = useState(0);
  
  // [OK] Event handlers disponibles
  const handleClick = () => setCount(count + 1);
  
  return (
    <button onClick={handleClick}>
      Count: {count}
    </button>
  );
}

// QUAND UTILISER:
// - Interactivité (clicks, forms, etc.)
// - Hooks (useState, useEffect, useContext, etc.)
// - Browser APIs (localStorage, window, etc.)
// - Event listeners


# === Composition Server + Client ===

// app/page.tsx (Server Component)
import { Counter } from '@/components/Counter'; // Client Component
import { Header } from '@/components/Header';   // Server Component

async function HomePage() {
  const data = await fetchData(); // Côté serveur
  
  return (
    <div>
      <Header data={data} />      {/* Server Component */}
      <Counter />                  {/* Client Component */}
      <Footer />                   {/* Server Component */}
    </div>
  );
}

// RÈGLES DE COMPOSITION:
// [OK] Server Component peut importer Client Component
// [X] Client Component NE PEUT PAS importer Server Component directement
// [OK] Mais peut recevoir Server Component comme children/props


// [X] MAUVAIS: Client importe Server directement
'use client';
import ServerComponent from './ServerComponent'; // Erreur!

function ClientComponent() {
  return <ServerComponent />;
}


// [OK] BON: Passer Server Component comme children
// app/page.tsx (Server)
import ClientWrapper from './ClientWrapper';
import ServerComponent from './ServerComponent';

function Page() {
  return (
    <ClientWrapper>
      <ServerComponent /> {/* Rendu côté serveur */}
    </ClientWrapper>
  );
}

// ClientWrapper.tsx
'use client';
function ClientWrapper({ children }) {
  const [open, setOpen] = useState(false);
  return <div onClick={() => setOpen(!open)}>{children}</div>;
}


# === Data Fetching dans Server Components ===

// Fetch parallèle
async function Dashboard() {
  // Les deux requêtes s'exécutent en parallèle
  const [users, posts] = await Promise.all([
    fetch('https://api.example.com/users').then(r => r.json()),
    fetch('https://api.example.com/posts').then(r => r.json())
  ]);
  
  return (
    <div>
      <UserList users={users} />
      <PostList posts={posts} />
    </div>
  );
}


// Fetch séquentiel (quand nécessaire)
async function UserProfile({ userId }) {
  const user = await fetchUser(userId);
  const posts = await fetchUserPosts(user.id); // Dépend de user
  
  return (
    <div>
      <h1>{user.name}</h1>
      <PostList posts={posts} />
    </div>
  );
}


// Cache et revalidation (Next.js)
async function Posts() {
  const posts = await fetch('https://api.example.com/posts', {
    next: { revalidate: 60 } // Cache 60 secondes
  });
  
  return <PostList posts={posts} />;
}

// Opt-out du cache
const posts = await fetch('https://api.example.com/posts', {
  cache: 'no-store' // Pas de cache
});

// Tag-based revalidation
const posts = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] }
});

// Plus tard, invalider ce tag:
import { revalidateTag } from 'next/cache';
revalidateTag('posts');


# === Server Actions (Mutations côté serveur) ===

// app/actions.ts
'use server'; // Marque toutes les fonctions comme Server Actions

export async function createUser(formData: FormData) {
  const name = formData.get('name');
  const email = formData.get('email');
  
  // Accès direct à la DB
  const user = await db.users.create({
    data: { name, email }
  });
  
  // Revalider cache
  revalidatePath('/users');
  
  return { success: true, user };
}


// Utilisation dans Client Component
'use client';
import { createUser } from './actions';

function CreateUserForm() {
  return (
    <form action={createUser}>
      <input name="name" required />
      <input name="email" type="email" required />
      <button type="submit">Create User</button>
    </form>
  );
}


// Server Action inline
function TodoForm() {
  async function createTodo(formData: FormData) {
    'use server';
    
    const text = formData.get('text');
    await db.todos.create({ data: { text } });
    revalidatePath('/todos');
  }
  
  return (
    <form action={createTodo}>
      <input name="text" />
      <button>Add</button>
    </form>
  );
}


// Avec useTransition (feedback immédiat)
'use client';
import { useTransition } from 'react';
import { createUser } from './actions';

function CreateUserForm() {
  const [isPending, startTransition] = useTransition();
  
  async function handleSubmit(formData: FormData) {
    startTransition(async () => {
      await createUser(formData);
    });
  }
  
  return (
    <form action={handleSubmit}>
      <input name="name" />
      <button disabled={isPending}>
        {isPending ? 'Creating...' : 'Create'}
      </button>
    </form>
  );
}


# === Streaming avec Suspense ===

// app/page.tsx
import { Suspense } from 'react';

function Page() {
  return (
    <div>
      <Header />
      
      {/* Posts stream quand prêts */}
      <Suspense fallback={<PostsSkeleton />}>
        <Posts />
      </Suspense>
      
      {/* Comments stream indépendamment */}
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments />
      </Suspense>
      
      <Footer />
    </div>
  );
}

// Server Component async
async function Posts() {
  const posts = await fetchPosts(); // Peut être lent
  return <PostList posts={posts} />;
}

// AVANTAGE:
// - Header et Footer s'affichent immédiatement
// - Posts et Comments streament dès qu'ils sont prêts
// - Pas besoin d'attendre que tout soit prêt


# === Patterns avancés ===

// Preload pattern (éviter waterfall)
// utils/data.ts
const userCache = new Map();

export function preloadUser(userId: string) {
  void fetchUser(userId); // Démarre fetch sans attendre
}

export async function fetchUser(userId: string) {
  if (!userCache.has(userId)) {
    userCache.set(
      userId,
      fetch(`/api/users/${userId}`).then(r => r.json())
    );
  }
  return userCache.get(userId);
}

// app/user/[id]/page.tsx
import { preloadUser, fetchUser } from '@/utils/data';

function UserPage({ params }) {
  preloadUser(params.id);
  return <UserProfile userId={params.id} />;
}

async function UserProfile({ userId }) {
  const user = await fetchUser(userId); // Déjà en cours!
  return <div>{user.name}</div>;
}


// Partial Prerendering (expérimental)
// next.config.js
module.exports = {
  experimental: {
    ppr: true
  }
};

// Combine static + dynamic dans même route
export const experimental_ppr = true;

function Page() {
  return (
    <div>
      <StaticHeader />       {/* Prerendu */}
      
      <Suspense fallback={<Skeleton />}>
        <DynamicContent />   {/* Streamed */}
      </Suspense>
      
      <StaticFooter />       {/* Prerendu */}
    </div>
  );
}



[OK] REACT 19 - NOUVELLES FONCTIONNALITÉS


React 19 introduit plusieurs nouvelles APIs et améliorations majeures.


# === use() Hook ===

Le hook `use()` permet de "lire" des Promises et Contexts directement,
même dans des conditions (contrairement aux autres hooks).

import { use } from 'react';

// Lire une Promise
function UserProfile({ userPromise }) {
  const user = use(userPromise); // Suspend jusqu'à résolution
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

// Utilisation avec Suspense
function App() {
  const userPromise = fetchUser('123');
  
  return (
    <Suspense fallback={<Spinner />}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  );
}


// [OK] AVANTAGE: use() peut être appelé conditionnellement!
function Component({ showUser, userPromise }) {
  if (showUser) {
    const user = use(userPromise); // [OK] OK dans condition
    return <div>{user.name}</div>;
  }
  return <div>No user</div>;
}

// Avec useState: [X] IMPOSSIBLE
function Component({ showUser }) {
  if (showUser) {
    const [user, setUser] = useState(null); // [X] Erreur!
  }
}


// Lire un Context avec use()
const ThemeContext = createContext('light');

function Button({ primary }) {
  let theme = 'light';
  
  // [OK] Conditionnel possible
  if (primary) {
    theme = use(ThemeContext);
  }
  
  return <button className={theme}>Click</button>;
}


// Lire multiple Promises
function Dashboard() {
  const users = use(fetchUsers());
  const posts = use(fetchPosts());
  const stats = use(fetchStats());
  
  return (
    <div>
      <UserList users={users} />
      <PostList posts={posts} />
      <Stats data={stats} />
    </div>
  );
}


# === useFormStatus() Hook ===

Hook spécifique aux formulaires pour accéder au statut de soumission.
Doit être utilisé dans un composant enfant du <form>.

import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending, data, method, action } = useFormStatus();
  
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  );
}

function ContactForm() {
  async function submitForm(formData) {
    await fetch('/api/contact', {
      method: 'POST',
      body: formData
    });
  }
  
  return (
    <form action={submitForm}>
      <input name="email" type="email" />
      <input name="message" />
      <SubmitButton /> {/* A accès au statut */}
    </form>
  );
}


// Exemple avancé avec feedback visuel
function SubmitButton() {
  const { pending, data } = useFormStatus();
  
  return (
    <button
      type="submit"
      disabled={pending}
      className={pending ? 'submitting' : ''}
    >
      {pending ? (
        <>
          <Spinner />
          Sending...
        </>
      ) : (
        'Send Message'
      )}
    </button>
  );
}


// Plusieurs boutons de soumission
function Form() {
  async function handleAction(formData) {
    const action = formData.get('action');
    
    if (action === 'save') {
      await saveDraft(formData);
    } else if (action === 'publish') {
      await publishPost(formData);
    }
  }
  
  return (
    <form action={handleAction}>
      <input name="title" />
      <textarea name="content" />
      
      <SaveButton />
      <PublishButton />
    </form>
  );
}

function SaveButton() {
  const { pending, data } = useFormStatus();
  const isSaving = pending && data?.get('action') === 'save';
  
  return (
    <button name="action" value="save" disabled={pending}>
      {isSaving ? 'Saving...' : 'Save Draft'}
    </button>
  );
}


# === useOptimistic() Hook ===

Permet des mises à jour optimistes de l'UI avant confirmation du serveur.

import { useOptimistic } from 'react';

function TodoList({ todos }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo) => {
      // Fonction qui applique la mise à jour optimiste
      return [...state, { ...newTodo, pending: true }];
    }
  );
  
  async function createTodo(formData) {
    const text = formData.get('text');
    const tempId = crypto.randomUUID();
    
    // [OK] Mise à jour optimiste immédiate (UI responsive)
    addOptimisticTodo({ id: tempId, text });
    
    // Requête serveur
    await fetch('/api/todos', {
      method: 'POST',
      body: JSON.stringify({ text })
    });
    
    // React réconcilie automatiquement avec la vraie réponse
  }
  
  return (
    <div>
      <form action={createTodo}>
        <input name="text" placeholder="New todo" />
        <button>Add</button>
      </form>
      
      <ul>
        {optimisticTodos.map(todo => (
          <li
            key={todo.id}
            style={{ opacity: todo.pending ? 0.5 : 1 }}
          >
            {todo.text}
            {todo.pending && ' (Saving...)'}
          </li>
        ))}
      </ul>
    </div>
  );
}


// Exemple avec suppression optimiste
function MessageList({ messages }) {
  const [optimisticMessages, deleteOptimisticMessage] = useOptimistic(
    messages,
    (state, deletedId) => state.filter(m => m.id !== deletedId)
  );
  
  async function deleteMessage(id) {
    // Suppression optimiste immédiate
    deleteOptimisticMessage(id);
    
    try {
      await fetch(`/api/messages/${id}`, { method: 'DELETE' });
    } catch (error) {
      // En cas d'erreur, React restaure l'état précédent
      console.error('Failed to delete:', error);
    }
  }
  
  return (
    <div>
      {optimisticMessages.map(msg => (
        <div key={msg.id}>
          {msg.text}
          <button onClick={() => deleteMessage(msg.id)}>Delete</button>
        </div>
      ))}
    </div>
  );
}


// Mise à jour optimiste complexe (like/unlike)
function Post({ post, isLiked }) {
  const [optimisticLike, setOptimisticLike] = useOptimistic(
    { liked: isLiked, count: post.likesCount },
    (state, newLiked) => ({
      liked: newLiked,
      count: state.count + (newLiked ? 1 : -1)
    })
  );
  
  async function toggleLike() {
    const newLiked = !optimisticLike.liked;
    
    // Mise à jour optimiste
    setOptimisticLike(newLiked);
    
    // Requête serveur
    await fetch(`/api/posts/${post.id}/like`, {
      method: newLiked ? 'POST' : 'DELETE'
    });
  }
  
  return (
    <div>
      <p>{post.content}</p>
      <button onClick={toggleLike}>
        {optimisticLike.liked ? '[HEAVY_BLACK_HEART]' : '[WHITE_HEART]'} {optimisticLike.count}
      </button>
    </div>
  );
}


# === useActionState() Hook (anciennement useFormState) ===

Gérer l'état d'une action asynchrone avec état initial et réponse.

import { useActionState } from 'react';

async function updateName(prevState, formData) {
  const name = formData.get('name');
  
  try {
    await updateUser({ name });
    return { success: true, message: 'Name updated!' };
  } catch (error) {
    return { success: false, message: error.message };
  }
}

function UpdateNameForm() {
  const [state, formAction, isPending] = useActionState(
    updateName,
    { success: false, message: '' } // État initial
  );
  
  return (
    <form action={formAction}>
      <input name="name" required />
      
      <button disabled={isPending}>
        {isPending ? 'Updating...' : 'Update Name'}
      </button>
      
      {state.message && (
        <p className={state.success ? 'success' : 'error'}>
          {state.message}
        </p>
      )}
    </form>
  );
}


# === ref as prop (plus besoin de forwardRef!) ===

En React 19, les refs peuvent être passées directement comme props.

// [X] React 18: forwardRef obligatoire
import { forwardRef } from 'react';

const Input = forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});


// [OK] React 19: ref comme prop normale
function Input({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

// Utilisation identique
function Parent() {
  const inputRef = useRef(null);
  
  return <Input ref={inputRef} />;
}


# === Metadata et document head ===

// React 19 permet de définir <title>, <meta>, etc. directement
function BlogPost({ post }) {
  return (
    <article>
      <title>{post.title} - My Blog</title>
      <meta name="description" content={post.excerpt} />
      <meta property="og:title" content={post.title} />
      
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

// React hoise automatiquement dans <head>


# === Améliorations des stylesheets et assets ===

// Stylesheets avec priority et precedence
import './styles.css' assert { type: 'css', precedence: 'high' };

function Component() {
  return (
    <>
      <link rel="stylesheet" href="style.css" precedence="default" />
      <div>Content</div>
    </>
  );
}


# === Preload et preinit ===

import { preload, preinit } from 'react-dom';

function Component() {
  // Preload: télécharger ressource
  preload('/api/data', { as: 'fetch' });
  preload('/font.woff2', { as: 'font' });
  
  // Preinit: télécharger ET exécuter
  preinit('/analytics.js', { as: 'script' });
  
  return <div>Content</div>;
}



[OK] WEB WORKERS AVEC REACT


Les Web Workers permettent d'exécuter du JavaScript en arrière-plan,
sans bloquer le thread principal (UI).


# === Worker basique ===

// worker.js
self.addEventListener('message', (event) => {
  const { type, data } = event.data;
  
  if (type === 'COMPUTE') {
    // Calcul intensif
    const result = heavyComputation(data);
    
    // Envoyer résultat
    self.postMessage({ type: 'RESULT', result });
  }
});

function heavyComputation(data) {
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += Math.sqrt(i) * data;
  }
  return result;
}


// Component React
import { useEffect, useRef, useState } from 'react';

function HeavyComputationComponent() {
  const [result, setResult] = useState(null);
  const [isComputing, setIsComputing] = useState(false);
  const workerRef = useRef(null);
  
  useEffect(() => {
    // Créer worker
    workerRef.current = new Worker(
      new URL('./worker.js', import.meta.url)
    );
    
    // Écouter messages du worker
    workerRef.current.onmessage = (event) => {
      if (event.data.type === 'RESULT') {
        setResult(event.data.result);
        setIsComputing(false);
      }
    };
    
    // Cleanup
    return () => {
      workerRef.current?.terminate();
    };
  }, []);
  
  const startComputation = () => {
    setIsComputing(true);
    workerRef.current.postMessage({
      type: 'COMPUTE',
      data: 42
    });
  };
  
  return (
    <div>
      <button onClick={startComputation} disabled={isComputing}>
        {isComputing ? 'Computing...' : 'Start Heavy Computation'}
      </button>
      
      {result !== null && <p>Result: {result}</p>}
    </div>
  );
}


# === Hook personnalisé useWorker ===

function useWorker(workerUrl) {
  const [result, setResult] = useState(null);
  const [error, setError] = useState(null);
  const [isRunning, setIsRunning] = useState(false);
  const workerRef = useRef(null);
  
  useEffect(() => {
    workerRef.current = new Worker(workerUrl);
    
    workerRef.current.onmessage = (e) => {
      setResult(e.data);
      setIsRunning(false);
    };
    
    workerRef.current.onerror = (e) => {
      setError(e.message);
      setIsRunning(false);
    };
    
    return () => workerRef.current?.terminate();
  }, [workerUrl]);
  
  const run = useCallback((data) => {
    setIsRunning(true);
    setError(null);
    workerRef.current.postMessage(data);
  }, []);
  
  return { result, error, isRunning, run };
}

// Utilisation
function Component() {
  const { result, isRunning, run } = useWorker(
    new URL('./worker.js', import.meta.url)
  );
  
  return (
    <div>
      <button onClick={() => run({ value: 100 })} disabled={isRunning}>
        Compute
      </button>
      {result && <p>Result: {result}</p>}
    </div>
  );
}


# === Worker avec Comlink (RPC simplifié) ===

// npm install comlink

// worker.js
import { expose } from 'comlink';

const api = {
  async processData(data) {
    // Traitement...
    return result;
  },
  
  async generateReport(params) {
    // Génération rapport...
    return report;
  }
};

expose(api);


// Component
import { wrap } from 'comlink';

function Component() {
  const [result, setResult] = useState(null);
  const workerRef = useRef(null);
  
  useEffect(() => {
    const worker = new Worker(
      new URL('./worker.js', import.meta.url),
      { type: 'module' }
    );
    
    workerRef.current = wrap(worker);
    
    return () => worker.terminate();
  }, []);
  
  const handleProcess = async () => {
    // Appel comme fonction normale!
    const data = await workerRef.current.processData({ value: 42 });
    setResult(data);
  };
  
  return (
    <button onClick={handleProcess}>Process</button>
  );
}


# === Worker Pool (multiple workers) ===

class WorkerPool {
  constructor(workerUrl, size = navigator.hardwareConcurrency || 4) {
    this.workers = [];
    this.availableWorkers = [];
    this.queue = [];
    
    for (let i = 0; i < size; i++) {
      const worker = new Worker(workerUrl);
      this.workers.push(worker);
      this.availableWorkers.push(worker);
    }
  }
  
  async exec(data) {
    return new Promise((resolve, reject) => {
      const task = { data, resolve, reject };
      
      if (this.availableWorkers.length > 0) {
        this.runTask(task);
      } else {
        this.queue.push(task);
      }
    });
  }
  
  runTask({ data, resolve, reject }) {
    const worker = this.availableWorkers.pop();
    
    const handleMessage = (event) => {
      worker.removeEventListener('message', handleMessage);
      this.availableWorkers.push(worker);
      
      // Traiter tâche suivante
      if (this.queue.length > 0) {
        this.runTask(this.queue.shift());
      }
      
      resolve(event.data);
    };
    
    worker.addEventListener('message', handleMessage);
    worker.addEventListener('error', reject);
    worker.postMessage(data);
  }
  
  terminate() {
    this.workers.forEach(w => w.terminate());
  }
}

// Utilisation
function Component() {
  const poolRef = useRef(null);
  
  useEffect(() => {
    poolRef.current = new WorkerPool(
      new URL('./worker.js', import.meta.url)
    );
    
    return () => poolRef.current.terminate();
  }, []);
  
  const processBatch = async (items) => {
    const results = await Promise.all(
      items.map(item => poolRef.current.exec(item))
    );
    return results;
  };
  
  return <div>...</div>;
}



[OK] MICRO-FRONTENDS AVEC REACT


Les micro-frontends permettent de découper une application en parties
indépendantes déployables séparément.


# === Module Federation (Webpack 5) ===

// app1/webpack.config.js (Host)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      
      // Exporter composants
      exposes: {
        './Button': './src/components/Button',
        './Header': './src/components/Header',
      },
      
      // Partager dépendances
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
      },
    }),
  ],
};


// app2/webpack.config.js (Consumer)
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'app2',
      
      // Importer depuis d'autres apps
      remotes: {
        app1: 'app1@http://localhost:3001/remoteEntry.js',
      },
      
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};


// Consommer composant distant
import { lazy, Suspense } from 'react';

const RemoteButton = lazy(() => import('app1/Button'));
const RemoteHeader = lazy(() => import('app1/Header'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading Header...</div>}>
        <RemoteHeader />
      </Suspense>
      
      <Suspense fallback={<div>Loading Button...</div>}>
        <RemoteButton onClick={() => console.log('Clicked!')}>
          Click me
        </RemoteButton>
      </Suspense>
    </div>
  );
}


# === Dynamic Remote Loading ===

// Charger remote dynamiquement à runtime
function loadRemoteComponent(scope, module) {
  return async () => {
    // Initialiser le remote container
    await __webpack_init_sharing__('default');
    const container = window[scope];
    await container.init(__webpack_share_scopes__.default);
    
    // Obtenir le factory du module
    const factory = await container.get(module);
    const Module = factory();
    
    return Module;
  };
}

// Utilisation
const RemoteButton = lazy(
  loadRemoteComponent('app1', './Button')
);


# === Iframe-based Micro-frontends ===

// Simple mais isolé (pas de partage d'état)
function MicroFrontend({ url, name }) {
  const iframeRef = useRef(null);
  
  useEffect(() => {
    const iframe = iframeRef.current;
    
    // Communication via postMessage
    const handleMessage = (event) => {
      if (event.origin !== new URL(url).origin) return;
      
      console.log('Message from', name, event.data);
    };
    
    window.addEventListener('message', handleMessage);
    
    return () => {
      window.removeEventListener('message', handleMessage);
    };
  }, [url, name]);
  
  const sendMessage = (data) => {
    const iframe = iframeRef.current;
    iframe.contentWindow.postMessage(data, new URL(url).origin);
  };
  
  return (
    <iframe
      ref={iframeRef}
      src={url}
      title={name}
      style={{ width: '100%', height: '500px', border: 'none' }}
    />
  );
}


# === Web Components (Custom Elements) ===

// app1 - Créer Web Component
class UserCardElement extends HTMLElement {
  connectedCallback() {
    const userId = this.getAttribute('user-id');
    const root = this.attachShadow({ mode: 'open' });
    
    // Render React dans le Web Component
    const container = document.createElement('div');
    root.appendChild(container);
    
    ReactDOM.createRoot(container).render(
      <UserCard userId={userId} />
    );
  }
}

customElements.define('user-card', UserCardElement);


// app2 - Utiliser Web Component
function App() {
  return (
    <div>
      <h1>Main App</h1>
      <user-card user-id="123" />
    </div>
  );
}


# === Single-SPA Framework ===

// npm install single-spa single-spa-react

// root-config.js
import { registerApplication, start } from 'single-spa';

registerApplication({
  name: '@myorg/navbar',
  app: () => System.import('@myorg/navbar'),
  activeWhen: '/',
});

registerApplication({
  name: '@myorg/dashboard',
  app: () => System.import('@myorg/dashboard'),
  activeWhen: '/dashboard',
});

registerApplication({
  name: '@myorg/settings',
  app: () => System.import('@myorg/settings'),
  activeWhen: '/settings',
});

start();


// navbar/src/root.component.js
import React from 'react';
import { BrowserRouter } from 'react-router-dom';

export default function Root(props) {
  return (
    <BrowserRouter>
      <nav>
        <a href="/dashboard">Dashboard</a>
        <a href="/settings">Settings</a>
      </nav>
    </BrowserRouter>
  );
}


// navbar/src/myorg-navbar.js
import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import Root from './root.component';

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  errorBoundary(err, info, props) {
    return <div>Error in navbar</div>;
  },
});

export const { bootstrap, mount, unmount } = lifecycles;


# === Shared State entre Micro-frontends ===

// Avec Custom Events
// app1 - Émettre événement
function publishEvent(eventName, data) {
  const event = new CustomEvent(eventName, { detail: data });
  window.dispatchEvent(event);
}

function App1() {
  const handleLogin = (user) => {
    publishEvent('user:login', user);
  };
  
  return <LoginForm onLogin={handleLogin} />;
}


// app2 - Écouter événement
function App2() {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    const handleLogin = (event) => {
      setUser(event.detail);
    };
    
    window.addEventListener('user:login', handleLogin);
    
    return () => {
      window.removeEventListener('user:login', handleLogin);
    };
  }, []);
  
  return <UserProfile user={user} />;
}


// Avec Shared Store (Redux, Zustand)
// shared-store.js
import create from 'zustand';

export const useSharedStore = create((set) => ({
  user: null,
  theme: 'light',
  setUser: (user) => set({ user }),
  setTheme: (theme) => set({ theme }),
}));


// app1
import { useSharedStore } from '@shared/store';

function App1() {
  const setUser = useSharedStore((state) => state.setUser);
  
  return <LoginForm onLogin={setUser} />;
}


// app2
import { useSharedStore } from '@shared/store';

function App2() {
  const user = useSharedStore((state) => state.user);
  
  return <UserProfile user={user} />;
}



[OK] ANIMATIONS AVANCÉES


# === Framer Motion - Animations déclaratives ===

// npm install framer-motion

import { motion, AnimatePresence } from 'framer-motion';

// Animation basique
function SimpleAnimation() {
  return (
    <motion.div
      initial={{ opacity: 0, y: -50 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: 50 }}
      transition={{ duration: 0.5 }}
    >
      Hello World
    </motion.div>
  );
}


// Variants (animations réutilisables)
const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1
    }
  }
};

const itemVariants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 }
};

function List({ items }) {
  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="visible"
    >
      {items.map(item => (
        <motion.li key={item.id} variants={itemVariants}>
          {item.text}
        </motion.li>
      ))}
    </motion.ul>
  );
}


// AnimatePresence (entrée/sortie)
function Modal({ isOpen, onClose }) {
  return (
    <AnimatePresence>
      {isOpen && (
        <>
          <motion.div
            className="backdrop"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            onClick={onClose}
          />
          
          <motion.div
            className="modal"
            initial={{ opacity: 0, scale: 0.8 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: 0.8 }}
            transition={{ type: 'spring', damping: 25 }}
          >
            <h2>Modal Content</h2>
            <button onClick={onClose}>Close</button>
          </motion.div>
        </>
      )}
    </AnimatePresence>
  );
}


// Layout animations (automatiques!)
function ExpandableCard({ isExpanded, onToggle }) {
  return (
    <motion.div layout onClick={onToggle}>
      <motion.h2 layout>Title</motion.h2>
      {isExpanded && (
        <motion.p
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
        >
          Additional content...
        </motion.p>
      )}
    </motion.div>
  );
}


// Shared layout animations
function Gallery({ images, selectedId, onSelect }) {
  return (
    <div className="gallery">
      {images.map(image => (
        <motion.div
          layoutId={image.id}
          key={image.id}
          onClick={() => onSelect(image.id)}
        >
          <img src={image.url} alt={image.title} />
        </motion.div>
      ))}
      
      <AnimatePresence>
        {selectedId && (
          <motion.div
            layoutId={selectedId}
            className="modal"
            onClick={() => onSelect(null)}
          >
            <motion.img
              src={images.find(i => i.id === selectedId).url}
              alt="Selected"
            />
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}


// Gestures (drag, hover, tap)
function DraggableBox() {
  return (
    <motion.div
      drag
      dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
      dragElastic={0.2}
      whileHover={{ scale: 1.1 }}
      whileTap={{ scale: 0.9 }}
      whileDrag={{ scale: 1.2 }}
    >
      Drag me!
    </motion.div>
  );
}


// Scroll animations
import { useScroll, useTransform } from 'framer-motion';

function ParallaxSection() {
  const { scrollYProgress } = useScroll();
  const y = useTransform(scrollYProgress, [0, 1], ['0%', '50%']);
  const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [1, 0.5, 0]);
  
  return (
    <motion.div style={{ y, opacity }}>
      Parallax content
    </motion.div>
  );
}


// Scroll-triggered animations
import { useInView } from 'framer-motion';

function AnimateOnScroll({ children }) {
  const ref = useRef(null);
  const isInView = useInView(ref, { once: true });
  
  return (
    <motion.div
      ref={ref}
      initial={{ opacity: 0, y: 50 }}
      animate={isInView ? { opacity: 1, y: 0 } : {}}
      transition={{ duration: 0.5 }}
    >
      {children}
    </motion.div>
  );
}


// Path animations (SVG)
function AnimatedLogo() {
  return (
    <svg width="100" height="100">
      <motion.path
        d="M 10 10 L 90 10 L 90 90 L 10 90 Z"
        stroke="#000"
        strokeWidth={2}
        fill="transparent"
        initial={{ pathLength: 0 }}
        animate={{ pathLength: 1 }}
        transition={{ duration: 2, ease: 'easeInOut' }}
      />
    </svg>
  );
}


// useMotionValue (contrôle manuel)
import { useMotionValue, useSpring } from 'framer-motion';

function DragSlider() {
  const x = useMotionValue(0);
  const springX = useSpring(x, { stiffness: 300, damping: 30 });
  
  return (
    <motion.div
      drag="x"
      dragConstraints={{ left: -200, right: 200 }}
      style={{ x: springX }}
    >
      Drag horizontally
    </motion.div>
  );
}


# === React Spring - Animations basées sur physique ===

// npm install @react-spring/web

import { useSpring, animated } from '@react-spring/web';

function SpringAnimation() {
  const [isVisible, setIsVisible] = useState(false);
  
  const styles = useSpring({
    opacity: isVisible ? 1 : 0,
    transform: isVisible ? 'translateY(0)' : 'translateY(-50px)',
    config: { tension: 280, friction: 60 }
  });
  
  return (
    <>
      <button onClick={() => setIsVisible(!isVisible)}>Toggle</button>
      <animated.div style={styles}>
        Animated content
      </animated.div>
    </>
  );
}


// useTransition (liste animée)
import { useTransition, animated } from '@react-spring/web';

function AnimatedList({ items }) {
  const transitions = useTransition(items, {
    from: { opacity: 0, transform: 'translateX(-100%)' },
    enter: { opacity: 1, transform: 'translateX(0%)' },
    leave: { opacity: 0, transform: 'translateX(100%)' },
    keys: item => item.id
  });
  
  return (
    <div>
      {transitions((style, item) => (
        <animated.div style={style}>
          {item.text}
        </animated.div>
      ))}
    </div>
  );
}


// useChain (séquencer animations)
import { useSpring, useChain, useSpringRef } from '@react-spring/web';

function SequencedAnimation() {
  const springRef = useSpringRef();
  const spring = useSpring({
    ref: springRef,
    from: { opacity: 0 },
    to: { opacity: 1 }
  });
  
  const transitionRef = useSpringRef();
  const transition = useTransition(items, {
    ref: transitionRef,
    // ...
  });
  
  useChain([springRef, transitionRef], [0, 0.3]);
  
  return <div>...</div>;
}


# === GSAP avec React ===

// npm install gsap

import { useEffect, useRef } from 'react';
import gsap from 'gsap';

function GSAPAnimation() {
  const boxRef = useRef(null);
  
  useEffect(() => {
    gsap.to(boxRef.current, {
      x: 100,
      rotation: 360,
      duration: 2,
      ease: 'bounce.out'
    });
  }, []);
  
  return <div ref={boxRef}>Animated Box</div>;
}


// ScrollTrigger
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);

function ScrollAnimation() {
  const triggerRef = useRef(null);
  
  useEffect(() => {
    gsap.to(triggerRef.current, {
      scrollTrigger: {
        trigger: triggerRef.current,
        start: 'top center',
        end: 'bottom center',
        scrub: true,
        markers: true
      },
      x: 500,
      rotation: 360
    });
  }, []);
  
  return <div ref={triggerRef}>Scroll to animate</div>;
}


// Timeline
function TimelineAnimation() {
  const tlRef = useRef(null);
  
  useEffect(() => {
    tlRef.current = gsap.timeline();
    
    tlRef.current
      .to('.box1', { x: 100, duration: 1 })
      .to('.box2', { y: 100, duration: 1 })
      .to('.box3', { rotation: 360, duration: 1 });
  }, []);
  
  return (
    <div>
      <div className="box1">Box 1</div>
      <div className="box2">Box 2</div>
      <div className="box3">Box 3</div>
    </div>
  );
}



[OK] INTERNATIONALISATION (i18n)


# === react-i18next (Solution complète) ===

// npm install react-i18next i18next

// i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  .use(Backend) // Charger traductions depuis serveur
  .use(LanguageDetector) // Détecter langue navigateur
  .use(initReactI18next)
  .init({
    fallbackLng: 'en',
    debug: true,
    
    interpolation: {
      escapeValue: false // React échappe déjà
    },
    
    resources: {
      en: {
        translation: {
          welcome: 'Welcome',
          greeting: 'Hello {{name}}!',
          itemCount: 'You have {{count}} item',
          itemCount_plural: 'You have {{count}} items',
          about: {
            title: 'About Us',
            description: 'We are a company...'
          }
        }
      },
      fr: {
        translation: {
          welcome: 'Bienvenue',
          greeting: 'Bonjour {{name}} !',
          itemCount: 'Vous avez {{count}} élément',
          itemCount_plural: 'Vous avez {{count}} éléments',
          about: {
            title: 'À propos',
            description: 'Nous sommes une entreprise...'
          }
        }
      },
      es: {
        translation: {
          welcome: 'Bienvenido',
          greeting: '¡Hola {{name}}!',
          itemCount: 'Tienes {{count}} elemento',
          itemCount_plural: 'Tienes {{count}} elementos'
        }
      }
    }
  });

export default i18n;


// index.js
import './i18n';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));


// Component basique
import { useTranslation } from 'react-i18next';

function Welcome() {
  const { t } = useTranslation();
  
  return (
    <div>
      <h1>{t('welcome')}</h1>
      <p>{t('greeting', { name: 'John' })}</p>
    </div>
  );
}


// Pluralisation
function ItemCount({ count }) {
  const { t } = useTranslation();
  
  return <p>{t('itemCount', { count })}</p>;
}


// Nested keys
function About() {
  const { t } = useTranslation();
  
  return (
    <div>
      <h1>{t('about.title')}</h1>
      <p>{t('about.description')}</p>
    </div>
  );
}


// Changer langue
function LanguageSelector() {
  const { i18n } = useTranslation();
  
  const changeLanguage = (lng) => {
    i18n.changeLanguage(lng);
  };
  
  return (
    <div>
      <button onClick={() => changeLanguage('en')}>English</button>
      <button onClick={() => changeLanguage('fr')}>Français</button>
      <button onClick={() => changeLanguage('es')}>Español</button>
      
      <p>Current language: {i18n.language}</p>
    </div>
  );
}


// Trans component (avec HTML)
import { Trans } from 'react-i18next';

// Translation: "Click <1>here</1> to learn more"
function Component() {
  return (
    <Trans i18nKey="clickHere">
      Click <a href="/learn">here</a> to learn more
    </Trans>
  );
}


// Namespaces (organiser traductions)
// common.json, home.json, dashboard.json
i18n.init({
  ns: ['common', 'home', 'dashboard'],
  defaultNS: 'common'
});

function Home() {
  const { t } = useTranslation(['home', 'common']);
  
  return (
    <div>
      <h1>{t('home:title')}</h1>
      <p>{t('common:welcome')}</p>
    </div>
  );
}


// Lazy loading de traductions
i18n.init({
  backend: {
    loadPath: '/locales/{{lng}}/{{ns}}.json'
  }
});


// Formatage dates et nombres
import { format } from 'date-fns';
import { fr, es } from 'date-fns/locale';

function FormattedDate({ date }) {
  const { i18n } = useTranslation();
  
  const locale = {
    fr: fr,
    es: es
  }[i18n.language] || undefined;
  
  const formatted = format(date, 'PPP', { locale });
  
  return <p>{formatted}</p>;
}


// react-intl alternative
// npm install react-intl

import { IntlProvider, FormattedMessage, FormattedNumber } from 'react-intl';

const messages = {
  en: {
    greeting: 'Hello {name}!'
  },
  fr: {
    greeting: 'Bonjour {name} !'
  }
};

function App() {
  const [locale, setLocale] = useState('en');
  
  return (
    <IntlProvider locale={locale} messages={messages[locale]}>
      <FormattedMessage id="greeting" values={{ name: 'John' }} />
      <FormattedNumber value={1000} style="currency" currency="USD" />
    </IntlProvider>
  );
}



[OK] PROGRESSIVE WEB APPS (PWA)


# === Configuration basique PWA ===

// public/manifest.json
{
  "short_name": "React App",
  "name": "My React Progressive Web App",
  "description": "A PWA built with React",
  "icons": [
    {
      "src": "icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    },
    {
      "src": "icon-maskable-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff",
  "orientation": "portrait",
  "categories": ["productivity", "utilities"],
  "screenshots": [
    {
      "src": "screenshot1.png",
      "sizes": "540x720",
      "type": "image/png"
    }
  ]
}


// public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="My PWA description" />
  
  <!-- PWA -->
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
  <link rel="apple-touch-icon" href="%PUBLIC_URL%/icon-192x192.png" />
  
  <title>React PWA</title>
</head>
<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
</body>
</html>


# === Service Worker ===

// public/service-worker.js
const CACHE_NAME = 'my-pwa-cache-v1';
const urlsToCache = [
  '/',
  '/static/css/main.css',
  '/static/js/main.js',
  '/logo192.png'
];

// Installation
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then((cache) => {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

// Activation
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          if (cacheName !== CACHE_NAME) {
            console.log('Deleting old cache:', cacheName);
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

// Fetch (stratégies de cache)
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((response) => {
        // Cache hit - retourner réponse
        if (response) {
          return response;
        }
        
        // Sinon, fetcher depuis réseau
        return fetch(event.request).then((response) => {
          // Vérifier si réponse valide
          if (!response || response.status !== 200 || response.type !== 'basic') {
            return response;
          }
          
          // Cloner et cacher
          const responseToCache = response.clone();
          
          caches.open(CACHE_NAME).then((cache) => {
            cache.put(event.request, responseToCache);
          });
          
          return response;
        });
      })
  );
});


// Enregistrer Service Worker
// src/serviceWorkerRegistration.js
export function register() {
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
      
      navigator.serviceWorker
        .register(swUrl)
        .then((registration) => {
          console.log('SW registered:', registration);
          
          // Vérifier mises à jour
          registration.onupdatefound = () => {
            const installingWorker = registration.installing;
            
            installingWorker.onstatechange = () => {
              if (installingWorker.state === 'installed') {
                if (navigator.serviceWorker.controller) {
                  console.log('New content available; please refresh.');
                } else {
                  console.log('Content cached for offline use.');
                }
              }
            };
          };
        })
        .catch((error) => {
          console.error('SW registration failed:', error);
        });
    });
  }
}

export function unregister() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.ready
      .then((registration) => {
        registration.unregister();
      })
      .catch((error) => {
        console.error(error.message);
      });
  }
}


// src/index.js
import * as serviceWorkerRegistration from './serviceWorkerRegistration';

serviceWorkerRegistration.register();


# === Stratégies de cache ===

// 1. Cache First (bonne pour assets statiques)
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});


// 2. Network First (bonne pour données dynamiques)
self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then((response) => {
        const responseClone = response.clone();
        caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request, responseClone);
        });
        return response;
      })
      .catch(() => {
        return caches.match(event.request);
      })
  );
});


// 3. Stale While Revalidate (afficher cache, mettre à jour en arrière-plan)
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.match(event.request).then((cachedResponse) => {
        const fetchPromise = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        });
        
        return cachedResponse || fetchPromise;
      });
    })
  );
});


// 4. Network Only (pas de cache)
self.addEventListener('fetch', (event) => {
  event.respondWith(fetch(event.request));
});


// 5. Cache Only (offline-first)
self.addEventListener('fetch', (event) => {
  event.respondWith(caches.match(event.request));
});


# === Hooks PWA ===

// useOnlineStatus
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  
  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  
  return isOnline;
}

// Utilisation
function App() {
  const isOnline = useOnlineStatus();
  
  return (
    <div>
      {!isOnline && (
        <div className="offline-banner">
          You are currently offline
        </div>
      )}
      <MainContent />
    </div>
  );
}


// useInstallPrompt (installer PWA)
function useInstallPrompt() {
  const [installPrompt, setInstallPrompt] = useState(null);
  const [isInstalled, setIsInstalled] = useState(false);
  
  useEffect(() => {
    const handleBeforeInstall = (e) => {
      e.preventDefault();
      setInstallPrompt(e);
    };
    
    const handleAppInstalled = () => {
      setIsInstalled(true);
      setInstallPrompt(null);
    };
    
    window.addEventListener('beforeinstallprompt', handleBeforeInstall);
    window.addEventListener('appinstalled', handleAppInstalled);
    
    return () => {
      window.removeEventListener('beforeinstallprompt', handleBeforeInstall);
      window.removeEventListener('appinstalled', handleAppInstalled);
    };
  }, []);
  
  const promptInstall = async () => {
    if (!installPrompt) return false;
    
    installPrompt.prompt();
    const result = await installPrompt.userChoice;
    
    if (result.outcome === 'accepted') {
      setInstallPrompt(null);
      return true;
    }
    
    return false;
  };
  
  return { installPrompt, promptInstall, isInstalled };
}

// Utilisation
function InstallButton() {
  const { installPrompt, promptInstall } = useInstallPrompt();
  
  if (!installPrompt) return null;
  
  return (
    <button onClick={promptInstall}>
      Install App
    </button>
  );
}


// useServiceWorker (détecter mises à jour)
function useServiceWorker() {
  const [waitingWorker, setWaitingWorker] = useState(null);
  const [showReload, setShowReload] = useState(false);
  
  useEffect(() => {
    if (!('serviceWorker' in navigator)) return;
    
    navigator.serviceWorker.ready.then((registration) => {
      registration.addEventListener('updatefound', () => {
        const newWorker = registration.installing;
        
        newWorker.addEventListener('statechange', () => {
          if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
            setWaitingWorker(newWorker);
            setShowReload(true);
          }
        });
      });
    });
  }, []);
  
  const reloadPage = () => {
    waitingWorker?.postMessage({ type: 'SKIP_WAITING' });
    setShowReload(false);
    window.location.reload();
  };
  
  return { showReload, reloadPage };
}

// Utilisation
function UpdateNotification() {
  const { showReload, reloadPage } = useServiceWorker();
  
  if (!showReload) return null;
  
  return (
    <div className="update-notification">
      <p>A new version is available!</p>
      <button onClick={reloadPage}>Update</button>
    </div>
  );
}


# === Background Sync (synchroniser hors ligne) ===

// Service Worker
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-posts') {
    event.waitUntil(syncPosts());
  }
});

async function syncPosts() {
  const cache = await caches.open('pending-requests');
  const requests = await cache.keys();
  
  return Promise.all(
    requests.map(async (request) => {
      try {
        await fetch(request.clone());
        await cache.delete(request);
      } catch (error) {
        console.error('Sync failed:', error);
      }
    })
  );
}


// React Component
function CreatePost() {
  const [title, setTitle] = useState('');
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    const post = { title, content: '...' };
    
    try {
      await fetch('/api/posts', {
        method: 'POST',
        body: JSON.stringify(post)
      });
    } catch (error) {
      // Stocker pour synchronisation ultérieure
      const cache = await caches.open('pending-requests');
      await cache.put(
        new Request('/api/posts', {
          method: 'POST',
          body: JSON.stringify(post)
        }),
        new Response()
      );
      
      // Enregistrer sync
      if ('sync' in registration) {
        await registration.sync.register('sync-posts');
      }
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input value={title} onChange={e => setTitle(e.target.value)} />
      <button>Create Post</button>
    </form>
  );
}


# === Push Notifications ===

// Demander permission
async function requestNotificationPermission() {
  const permission = await Notification.requestPermission();
  
  if (permission === 'granted') {
    console.log('Notification permission granted');
    return true;
  }
  
  return false;
}


// Subscribe aux push notifications
async function subscribeToPush() {
  const registration = await navigator.serviceWorker.ready;
  
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: 'YOUR_PUBLIC_VAPID_KEY'
  });
  
  // Envoyer subscription au serveur
  await fetch('/api/push/subscribe', {
    method: 'POST',
    body: JSON.stringify(subscription),
    headers: { 'Content-Type': 'application/json' }
  });
  
  return subscription;
}


// Service Worker - Recevoir push
self.addEventListener('push', (event) => {
  const data = event.data.json();
  
  const options = {
    body: data.body,
    icon: '/icon-192x192.png',
    badge: '/badge-72x72.png',
    vibrate: [200, 100, 200],
    data: {
      url: data.url
    },
    actions: [
      { action: 'open', title: 'Open' },
      { action: 'close', title: 'Close' }
    ]
  };
  
  event.waitUntil(
    self.registration.showNotification(data.title, options)
  );
});


// Gérer clic sur notification
self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  
  if (event.action === 'open') {
    event.waitUntil(
      clients.openWindow(event.notification.data.url)
    );
  }
});


// React Hook pour notifications
function useNotifications() {
  const [permission, setPermission] = useState(Notification.permission);
  const [subscription, setSubscription] = useState(null);
  
  const requestPermission = async () => {
    const result = await Notification.requestPermission();
    setPermission(result);
    
    if (result === 'granted') {
      const sub = await subscribeToPush();
      setSubscription(sub);
    }
  };
  
  return { permission, subscription, requestPermission };
}



[OK] PERFORMANCE MONITORING AVANCÉ


# === Web Vitals (métriques essentielles) ===

// npm install web-vitals

import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics({ name, value, id }) {
  // Envoyer à service analytics (Google Analytics, etc.)
  if (window.gtag) {
    window.gtag('event', name, {
      event_category: 'Web Vitals',
      event_label: id,
      value: Math.round(name === 'CLS' ? value * 1000 : value),
      non_interaction: true
    });
  }
  
  console.log(name, value);
}

// Mesurer toutes les métriques
getCLS(sendToAnalytics);  // Cumulative Layout Shift
getFID(sendToAnalytics);  // First Input Delay
getFCP(sendToAnalytics);  // First Contentful Paint
getLCP(sendToAnalytics);  // Largest Contentful Paint
getTTFB(sendToAnalytics); // Time to First Byte


// Hook personnalisé
function useWebVitals() {
  const [vitals, setVitals] = useState({});
  
  useEffect(() => {
    const handleVital = ({ name, value }) => {
      setVitals(prev => ({ ...prev, [name]: value }));
    };
    
    getCLS(handleVital);
    getFID(handleVital);
    getFCP(handleVital);
    getLCP(handleVital);
    getTTFB(handleVital);
  }, []);
  
  return vitals;
}

// Affichage debug
function PerformanceMetrics() {
  const vitals = useWebVitals();
  
  return (
    <div className="perf-metrics">
      <h3>Web Vitals</h3>
      <ul>
        {Object.entries(vitals).map(([name, value]) => (
          <li key={name}>
            {name}: {Math.round(value)}ms
          </li>
        ))}
      </ul>
    </div>
  );
}


# === Performance Observer API ===

// Mesurer temps de rendu composants
function useRenderTime(componentName) {
  useEffect(() => {
    const startTime = performance.now();
    
    return () => {
      const endTime = performance.now();
      const duration = endTime - startTime;
      
      console.log(`${componentName} render time: ${duration.toFixed(2)}ms`);
      
      // Envoyer à analytics si lent
      if (duration > 16) { // >16ms = dropped frame
        sendToAnalytics({
          name: 'slow-render',
          component: componentName,
          duration
        });
      }
    };
  });
}

// Utilisation
function HeavyComponent() {
  useRenderTime('HeavyComponent');
  
  return <div>...</div>;
}


// PerformanceObserver pour navigation timing
function setupNavigationObserver() {
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      console.log('Navigation timing:', {
        dns: entry.domainLookupEnd - entry.domainLookupStart,
        tcp: entry.connectEnd - entry.connectStart,
        ttfb: entry.responseStart - entry.requestStart,
        download: entry.responseEnd - entry.responseStart,
        domInteractive: entry.domInteractive - entry.fetchStart,
        domComplete: entry.domComplete - entry.fetchStart
      });
    }
  });
  
  observer.observe({ type: 'navigation', buffered: true });
}


// Observer ressources lentes
function setupResourceObserver() {
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (entry.duration > 1000) { // Plus de 1s
        console.warn('Slow resource:', {
          name: entry.name,
          type: entry.initiatorType,
          duration: entry.duration,
          size: entry.transferSize
        });
      }
    }
  });
  
  observer.observe({ type: 'resource', buffered: true });
}


# === React Profiler API (détaillé) ===

import { Profiler } from 'react';

function onRenderCallback(
  id,                    // ID du Profiler
  phase,                 // "mount" ou "update"
  actualDuration,        // Temps de rendu
  baseDuration,          // Temps estimé sans mémoïsation
  startTime,             // Timestamp de début
  commitTime,            // Timestamp de commit
  interactions           // Set d'interactions
) {
  console.log(`[${id}] ${phase}:`, {
    actualDuration: actualDuration.toFixed(2),
    baseDuration: baseDuration.toFixed(2),
    startTime: startTime.toFixed(2),
    commitTime: commitTime.toFixed(2)
  });
  
  // Envoyer si lent
  if (actualDuration > 16) {
    sendToAnalytics({
      name: 'slow-component',
      id,
      phase,
      duration: actualDuration
    });
  }
}

function App() {
  return (
    <div>
      <Profiler id="Navigation" onRender={onRenderCallback}>
        <Navigation />
      </Profiler>
      
      <Profiler id="Main" onRender={onRenderCallback}>
        <MainContent />
      </Profiler>
      
      <Profiler id="Sidebar" onRender={onRenderCallback}>
        <Sidebar />
      </Profiler>
    </div>
  );
}


// Hook pour profiling automatique
function useProfiler(id) {
  const renderCount = useRef(0);
  const totalDuration = useRef(0);
  
  const onRender = useCallback((
    profilerId,
    phase,
    actualDuration
  ) => {
    renderCount.current++;
    totalDuration.current += actualDuration;
    
    console.log(`[${id}] Render #${renderCount.current}:`, {
      phase,
      duration: actualDuration.toFixed(2),
      average: (totalDuration.current / renderCount.current).toFixed(2)
    });
  }, [id]);
  
  return { onRender, renderCount: renderCount.current };
}


# === Memory Profiling ===

// Détecter memory leaks
function useMemoryMonitor(interval = 5000) {
  useEffect(() => {
    if (!performance.memory) {
      console.warn('performance.memory not available');
      return;
    }
    
    const timer = setInterval(() => {
      const memory = performance.memory;
      
      console.log('Memory usage:', {
        used: (memory.usedJSHeapSize / 1048576).toFixed(2) + ' MB',
        total: (memory.totalJSHeapSize / 1048576).toFixed(2) + ' MB',
        limit: (memory.jsHeapSizeLimit / 1048576).toFixed(2) + ' MB'
      });
      
      // Alert si utilisation > 90%
      const usage = memory.usedJSHeapSize / memory.jsHeapSizeLimit;
      if (usage > 0.9) {
        console.error('High memory usage detected!', usage);
      }
    }, interval);
    
    return () => clearInterval(timer);
  }, [interval]);
}


# === Long Tasks Monitoring ===

function useLongTasksMonitor() {
  useEffect(() => {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        console.warn('Long task detected:', {
          duration: entry.duration,
          startTime: entry.startTime,
          name: entry.name
        });
        
        // Envoyer alerte si >50ms
        if (entry.duration > 50) {
          sendToAnalytics({
            name: 'long-task',
            duration: entry.duration
          });
        }
      }
    });
    
    observer.observe({ type: 'longtask', buffered: true });
    
    return () => observer.disconnect();
  }, []);
}


# === Bundle Size Analysis ===

// webpack-bundle-analyzer
// npm install --save-dev webpack-bundle-analyzer

// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
      reportFilename: 'bundle-report.html'
    })
  ]
};


// source-map-explorer (CRA)
// npm install --save-dev source-map-explorer

// package.json
{
  "scripts": {
    "analyze": "source-map-explorer 'build/static/js/*.js'"
  }
}



[CLASSICAL_BUILDING] ARCHITECTURE PATTERNS


# === Feature-Sliced Design ===

Structure d'application modulaire et scalable.

project/
├── src/
│   ├── app/                    # Configuration application
│   │   ├── providers/          # Providers globaux
│   │   ├── styles/             # Styles globaux
│   │   └── index.tsx
│   │
│   ├── pages/                  # Pages de l'application
│   │   ├── home/
│   │   ├── profile/
│   │   └── settings/
│   │
│   ├── widgets/                # Widgets (composants complexes)
│   │   ├── header/
│   │   ├── sidebar/
│   │   └── user-card/
│   │
│   ├── features/               # Features métier
│   │   ├── auth/
│   │   │   ├── ui/             # Composants UI
│   │   │   ├── model/          # État et logique
│   │   │   ├── api/            # API calls
│   │   │   └── lib/            # Utilitaires
│   │   ├── post-create/
│   │   └── comment-form/
│   │
│   ├── entities/               # Entités métier
│   │   ├── user/
│   │   │   ├── ui/
│   │   │   ├── model/
│   │   │   └── api/
│   │   ├── post/
│   │   └── comment/
│   │
│   └── shared/                 # Code partagé
│       ├── ui/                 # Composants UI réutilisables
│       ├── lib/                # Utilitaires
│       ├── api/                # Configuration API
│       └── config/             # Configuration


// Exemple: Feature auth
// features/auth/model/store.ts
import create from 'zustand';

interface AuthState {
  user: User | null;
  isAuthenticated: boolean;
  login: (credentials: Credentials) => Promise<void>;
  logout: () => void;
}

export const useAuthStore = create<AuthState>((set) => ({
  user: null,
  isAuthenticated: false,
  
  login: async (credentials) => {
    const user = await authApi.login(credentials);
    set({ user, isAuthenticated: true });
  },
  
  logout: () => {
    set({ user: null, isAuthenticated: false });
  }
}));


// features/auth/ui/LoginForm.tsx
import { useAuthStore } from '../model/store';

export function LoginForm() {
  const login = useAuthStore(state => state.login);
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    await login({ email, password });
  };
  
  return <form onSubmit={handleSubmit}>...</form>;
}


# === Clean Architecture (Ports & Adapters) ===

src/
├── domain/                     # Logique métier pure
│   ├── entities/
│   │   ├── User.ts
│   │   └── Post.ts
│   ├── use-cases/
│   │   ├── CreateUser.ts
│   │   └── GetUserPosts.ts
│   └── repositories/           # Interfaces
│       └── UserRepository.ts
│
├── infrastructure/             # Implémentations
│   ├── repositories/
│   │   └── ApiUserRepository.ts
│   ├── api/
│   │   └── client.ts
│   └── storage/
│       └── localStorage.ts
│
├── application/                # Orchestration
│   ├── hooks/
│   │   └── useUser.ts
│   └── services/
│       └── AuthService.ts
│
└── presentation/               # UI
    ├── components/
    ├── pages/
    └── styles/


// domain/entities/User.ts
export class User {
  constructor(
    public readonly id: string,
    public readonly email: string,
    public readonly name: string
  ) {}
  
  // Logique métier pure
  isAdmin(): boolean {
    return this.email.endsWith('@admin.com');
  }
  
  canEditPost(post: Post): boolean {
    return post.authorId === this.id || this.isAdmin();
  }
}


// domain/repositories/UserRepository.ts
export interface UserRepository {
  findById(id: string): Promise<User | null>;
  findAll(): Promise<User[]>;
  save(user: User): Promise<void>;
  delete(id: string): Promise<void>;
}


// infrastructure/repositories/ApiUserRepository.ts
import { UserRepository } from '@/domain/repositories/UserRepository';
import { User } from '@/domain/entities/User';

export class ApiUserRepository implements UserRepository {
  constructor(private apiClient: ApiClient) {}
  
  async findById(id: string): Promise<User | null> {
    const response = await this.apiClient.get(`/users/${id}`);
    if (!response) return null;
    
    return new User(response.id, response.email, response.name);
  }
  
  async findAll(): Promise<User[]> {
    const response = await this.apiClient.get('/users');
    return response.map(u => new User(u.id, u.email, u.name));
  }
  
  async save(user: User): Promise<void> {
    await this.apiClient.post('/users', {
      id: user.id,
      email: user.email,
      name: user.name
    });
  }
  
  async delete(id: string): Promise<void> {
    await this.apiClient.delete(`/users/${id}`);
  }
}


// domain/use-cases/GetUserPosts.ts
export class GetUserPosts {
  constructor(
    private userRepository: UserRepository,
    private postRepository: PostRepository
  ) {}
  
  async execute(userId: string): Promise<Post[]> {
    const user = await this.userRepository.findById(userId);
    if (!user) throw new Error('User not found');
    
    const posts = await this.postRepository.findByAuthorId(userId);
    return posts;
  }
}


// application/hooks/useUser.ts
import { useQuery } from '@tanstack/react-query';
import { getUserById } from '../services/UserService';

export function useUser(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => getUserById(userId)
  });
}


// presentation/pages/UserProfile.tsx
import { useUser } from '@/application/hooks/useUser';

export function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading } = useUser(userId);
  
  if (isLoading) return <Spinner />;
  if (!user) return <NotFound />;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      {user.isAdmin() && <AdminBadge />}
    </div>
  );
}


# === Container/Presentational Pattern ===

// Container (logique)
function UserListContainer() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [filter, setFilter] = useState('all');
  
  useEffect(() => {
    fetchUsers().then(data => {
      setUsers(data);
      setLoading(false);
    });
  }, []);
  
  const filteredUsers = useMemo(() => {
    return users.filter(user => {
      if (filter === 'active') return user.isActive;
      if (filter === 'inactive') return !user.isActive;
      return true;
    });
  }, [users, filter]);
  
  const handleDelete = async (id) => {
    await deleteUser(id);
    setUsers(users.filter(u => u.id !== id));
  };
  
  return (
    <UserListPresentation
      users={filteredUsers}
      loading={loading}
      filter={filter}
      onFilterChange={setFilter}
      onDelete={handleDelete}
    />
  );
}


// Presentational (UI pure)
interface UserListPresentationProps {
  users: User[];
  loading: boolean;
  filter: string;
  onFilterChange: (filter: string) => void;
  onDelete: (id: string) => void;
}

function UserListPresentation({
  users,
  loading,
  filter,
  onFilterChange,
  onDelete
}: UserListPresentationProps) {
  if (loading) return <Spinner />;
  
  return (
    <div>
      <FilterBar value={filter} onChange={onFilterChange} />
      
      <ul>
        {users.map(user => (
          <UserCard
            key={user.id}
            user={user}
            onDelete={() => onDelete(user.id)}
          />
        ))}
      </ul>
    </div>
  );
}


# === Dependency Injection ===

// Context-based DI
interface Services {
  userService: UserService;
  postService: PostService;
  authService: AuthService;
}

const ServicesContext = createContext<Services | null>(null);

export function ServicesProvider({ children }: { children: ReactNode }) {
  const services: Services = useMemo(() => ({
    userService: new UserService(new ApiUserRepository()),
    postService: new PostService(new ApiPostRepository()),
    authService: new AuthService()
  }), []);
  
  return (
    <ServicesContext.Provider value={services}>
      {children}
    </ServicesContext.Provider>
  );
}

export function useServices() {
  const context = useContext(ServicesContext);
  if (!context) {
    throw new Error('useServices must be used within ServicesProvider');
  }
  return context;
}


// Utilisation
function UserProfile({ userId }: { userId: string }) {
  const { userService } = useServices();
  const [user, setUser] = useState<User | null>(null);
  
  useEffect(() => {
    userService.getUserById(userId).then(setUser);
  }, [userId, userService]);
  
  return user ? <div>{user.name}</div> : <Spinner />;
}


# === Repository Pattern ===

// Abstract Repository
interface Repository<T> {
  findAll(): Promise<T[]>;
  findById(id: string): Promise<T | null>;
  create(entity: T): Promise<T>;
  update(id: string, entity: Partial<T>): Promise<T>;
  delete(id: string): Promise<void>;
}


// Generic Repository Implementation
class ApiRepository<T> implements Repository<T> {
  constructor(
    private endpoint: string,
    private entityMapper: (data: any) => T
  ) {}
  
  async findAll(): Promise<T[]> {
    const response = await fetch(`/api/${this.endpoint}`);
    const data = await response.json();
    return data.map(this.entityMapper);
  }
  
  async findById(id: string): Promise<T | null> {
    const response = await fetch(`/api/${this.endpoint}/${id}`);
    if (!response.ok) return null;
    const data = await response.json();
    return this.entityMapper(data);
  }
  
  async create(entity: T): Promise<T> {
    const response = await fetch(`/api/${this.endpoint}`, {
      method: 'POST',
      body: JSON.stringify(entity)
    });
    const data = await response.json();
    return this.entityMapper(data);
  }
  
  async update(id: string, entity: Partial<T>): Promise<T> {
    const response = await fetch(`/api/${this.endpoint}/${id}`, {
      method: 'PUT',
      body: JSON.stringify(entity)
    });
    const data = await response.json();
    return this.entityMapper(data);
  }
  
  async delete(id: string): Promise<void> {
    await fetch(`/api/${this.endpoint}/${id}`, { method: 'DELETE' });
  }
}


// Specific Repository
const userRepository = new ApiRepository<User>(
  'users',
  (data) => new User(data.id, data.email, data.name)
);

const postRepository = new ApiRepository<Post>(
  'posts',
  (data) => new Post(data.id, data.title, data.content, data.authorId)
);



[OK] SÉCURITÉ AVANCÉE


# === Content Security Policy (CSP) ===

// public/index.html
<meta
  http-equiv="Content-Security-Policy"
  content="
    default-src 'self';
    script-src 'self' 'unsafe-inline' https://trusted-cdn.com;
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https:;
    font-src 'self' data:;
    connect-src 'self' https://api.example.com;
    frame-ancestors 'none';
  "
/>


// Avec Next.js (next.config.js)
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
  },
  {
    key: 'X-Frame-Options',
    value: 'DENY'
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff'
  },
  {
    key: 'Referrer-Policy',
    value: 'strict-origin-when-cross-origin'
  },
  {
    key: 'Permissions-Policy',
    value: 'camera=(), microphone=(), geolocation=()'
  }
];

module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: securityHeaders
      }
    ];
  }
};


# === XSS Protection ===

// [X] DANGEREUX: dangerouslySetInnerHTML sans sanitization
function Comment({ comment }) {
  return <div dangerouslySetInnerHTML={{ __html: comment.html }} />;
}


// [OK] BON: Sanitizer avec DOMPurify
import DOMPurify from 'dompurify';

function Comment({ comment }) {
  const sanitizedHTML = DOMPurify.sanitize(comment.html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
    ALLOWED_ATTR: ['href']
  });
  
  return <div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />;
}


// Configuration DOMPurify stricte
const cleanHTML = DOMPurify.sanitize(userInput, {
  ALLOWED_TAGS: ['p', 'br', 'strong', 'em'],
  ALLOWED_ATTR: [],
  KEEP_CONTENT: true,
  RETURN_TRUSTED_TYPE: true
});


// Hook personnalisé
function useSanitizedHTML(html: string) {
  return useMemo(() => {
    return DOMPurify.sanitize(html, {
      ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li'],
      ALLOWED_ATTR: ['href', 'target', 'rel']
    });
  }, [html]);
}


# === CSRF Protection ===

// Avec tokens CSRF
import Cookies from 'js-cookie';

async function makeSecureRequest(url: string, options: RequestInit = {}) {
  const csrfToken = Cookies.get('csrf_token');
  
  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'X-CSRF-Token': csrfToken || '',
      'Content-Type': 'application/json'
    },
    credentials: 'include' // Envoyer cookies
  });
}


// Hook pour requêtes sécurisées
function useSecureFetch() {
  const csrfToken = useMemo(() => Cookies.get('csrf_token'), []);
  
  const secureFetch = useCallback(async (url: string, options: RequestInit = {}) => {
    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'X-CSRF-Token': csrfToken || ''
      },
      credentials: 'include'
    });
  }, [csrfToken]);
  
  return secureFetch;
}


# === Secure Authentication ===

// JWT Storage (localStorage vs httpOnly cookies)

// [X] ÉVITER: localStorage (vulnérable XSS)
localStorage.setItem('token', jwt);

// [OK] MEILLEUR: httpOnly cookies (sécurisé contre XSS)
// Configuré côté serveur:
// Set-Cookie: token=...; HttpOnly; Secure; SameSite=Strict


// Token refresh automatique
function useAuthWithRefresh() {
  const [accessToken, setAccessToken] = useState<string | null>(null);
  const refreshTokenRef = useRef<NodeJS.Timeout | null>(null);
  
  const refreshToken = useCallback(async () => {
    try {
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        credentials: 'include' // Envoie refresh token (httpOnly cookie)
      });
      
      const { accessToken } = await response.json();
      setAccessToken(accessToken);
      
      // Programmer prochain refresh (avant expiration)
      const tokenPayload = JSON.parse(atob(accessToken.split('.')[1]));
      const expiresIn = tokenPayload.exp * 1000 - Date.now();
      const refreshIn = expiresIn - 60000; // 1 minute avant expiration
      
      refreshTokenRef.current = setTimeout(refreshToken, refreshIn);
    } catch (error) {
      console.error('Token refresh failed:', error);
      // Rediriger vers login
    }
  }, []);
  
  useEffect(() => {
    refreshToken();
    
    return () => {
      if (refreshTokenRef.current) {
        clearTimeout(refreshTokenRef.current);
      }
    };
  }, [refreshToken]);
  
  return accessToken;
}


# === Input Validation ===

// Validation côté client ET serveur (toujours!)

// Validation avec Zod
import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'Password must be 8+ characters'),
  age: z.number().min(18).max(120),
  username: z.string().regex(/^[a-zA-Z0-9_]+$/, 'Invalid username')
});

function SignupForm() {
  const [errors, setErrors] = useState<Record<string, string>>({});
  
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    
    const formData = new FormData(e.currentTarget);
    const data = {
      email: formData.get('email'),
      password: formData.get('password'),
      age: Number(formData.get('age')),
      username: formData.get('username')
    };
    
    try {
      // Valider données
      const validData = userSchema.parse(data);
      
      // Envoyer au serveur
      await fetch('/api/signup', {
        method: 'POST',
        body: JSON.stringify(validData)
      });
    } catch (error) {
      if (error instanceof z.ZodError) {
        const fieldErrors: Record<string, string> = {};
        error.errors.forEach(err => {
          if (err.path[0]) {
            fieldErrors[err.path[0].toString()] = err.message;
          }
        });
        setErrors(fieldErrors);
      }
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" />
      {errors.email && <span className="error">{errors.email}</span>}
      
      <input name="password" type="password" />
      {errors.password && <span className="error">{errors.password}</span>}
      
      <input name="age" type="number" />
      {errors.age && <span className="error">{errors.age}</span>}
      
      <input name="username" />
      {errors.username && <span className="error">{errors.username}</span>}
      
      <button type="submit">Sign Up</button>
    </form>
  );
}


# === Rate Limiting côté client ===

// Throttle requêtes pour éviter abus
function useRateLimitedFetch(maxRequests = 10, windowMs = 60000) {
  const requestsRef = useRef<number[]>([]);
  
  const rateLimitedFetch = useCallback(async (url: string, options?: RequestInit) => {
    const now = Date.now();
    
    // Nettoyer anciennes requêtes
    requestsRef.current = requestsRef.current.filter(
      time => now - time < windowMs
    );
    
    // Vérifier limite
    if (requestsRef.current.length >= maxRequests) {
      throw new Error('Rate limit exceeded. Please try again later.');
    }
    
    requestsRef.current.push(now);
    return fetch(url, options);
  }, [maxRequests, windowMs]);
  
  return rateLimitedFetch;
}


# === Secure File Upload ===

function SecureFileUpload() {
  const [error, setError] = useState<string>('');
  
  const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
  const MAX_SIZE = 5 * 1024 * 1024; // 5MB
  
  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;
    
    // Vérifier type
    if (!ALLOWED_TYPES.includes(file.type)) {
      setError('Invalid file type. Only JPEG, PNG, and GIF allowed.');
      return;
    }
    
    // Vérifier taille
    if (file.size > MAX_SIZE) {
      setError('File too large. Maximum size is 5MB.');
      return;
    }
    
    // Vérifier vraiment que c'est une image (magic numbers)
    const buffer = await file.arrayBuffer();
    const bytes = new Uint8Array(buffer);
    
    const isJPEG = bytes[0] === 0xFF && bytes[1] === 0xD8 && bytes[2] === 0xFF;
    const isPNG = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4E;
    const isGIF = bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46;
    
    if (!isJPEG && !isPNG && !isGIF) {
      setError('Invalid image file.');
      return;
    }
    
    // Upload sécurisé
    const formData = new FormData();
    formData.append('file', file);
    
    await fetch('/api/upload', {
      method: 'POST',
      body: formData
    });
  };
  
  return (
    <div>
      <input type="file" accept="image/*" onChange={handleFileChange} />
      {error && <p className="error">{error}</p>}
    </div>
  );
}


# === Dependency Security Audits ===

// Vérifier vulnérabilités
npm audit
npm audit fix

// Avec Snyk
npm install -g snyk
snyk auth
snyk test
snyk monitor


# === Environment Variables Sécurisées ===

// [X] DANGEREUX: Exposer secrets au client
// REACT_APP_API_KEY=secret123  <- Visible dans bundle!

// [OK] BON: Secrets côté serveur seulement
// Server-side (API routes)
const API_KEY = process.env.API_KEY; // Pas exposé au client

// Client-side (uniquement données publiques)
const PUBLIC_API_URL = process.env.REACT_APP_PUBLIC_API_URL;


// Vérifier si secret exposé accidentellement
useEffect(() => {
  if (process.env.NODE_ENV === 'development') {
    const exposedSecrets = Object.keys(process.env)
      .filter(key => key.includes('SECRET') || key.includes('KEY'))
      .filter(key => key.startsWith('REACT_APP_'));
    
    if (exposedSecrets.length > 0) {
      console.error('[ATTENTION] SECURITY WARNING: Secrets exposed to client:', exposedSecrets);
    }
  }
}, []);



[OK] TESTING AVANCÉ


# === Integration Testing ===

// Test avec MSW (Mock Service Worker)
// npm install --save-dev msw

// mocks/handlers.ts
import { rest } from 'msw';

export const handlers = [
  rest.get('/api/users', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json([
        { id: 1, name: 'John Doe', email: 'john@example.com' },
        { id: 2, name: 'Jane Doe', email: 'jane@example.com' }
      ])
    );
  }),
  
  rest.post('/api/users', async (req, res, ctx) => {
    const body = await req.json();
    
    return res(
      ctx.status(201),
      ctx.json({ id: 3, ...body })
    );
  }),
  
  rest.delete('/api/users/:id', (req, res, ctx) => {
    const { id } = req.params;
    
    return res(
      ctx.status(204)
    );
  })
];


// mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);


// setupTests.ts
import { server } from './mocks/server';

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());


// Test avec données mockées
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { server } from './mocks/server';
import { rest } from 'msw';
import UserList from './UserList';

test('loads and displays users', async () => {
  render(<UserList />);
  
  expect(screen.getByText(/loading/i)).toBeInTheDocument();
  
  await waitFor(() => {
    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.getByText('Jane Doe')).toBeInTheDocument();
  });
});


test('handles server error', async () => {
  server.use(
    rest.get('/api/users', (req, res, ctx) => {
      return res(ctx.status(500), ctx.json({ message: 'Server error' }));
    })
  );
  
  render(<UserList />);
  
  await waitFor(() => {
    expect(screen.getByText(/error/i)).toBeInTheDocument();
  });
});


test('creates new user', async () => {
  const user = userEvent.setup();
  render(<UserList />);
  
  await waitFor(() => {
    expect(screen.getByText('John Doe')).toBeInTheDocument();
  });
  
  const input = screen.getByLabelText(/name/i);
  await user.type(input, 'New User');
  
  const button = screen.getByRole('button', { name: /add/i });
  await user.click(button);
  
  await waitFor(() => {
    expect(screen.getByText('New User')).toBeInTheDocument();
  });
});


# === E2E Testing avec Playwright ===

// npm install --save-dev @playwright/test

// tests/e2e/login.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Login Flow', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('http://localhost:3000');
  });
  
  test('should login successfully', async ({ page }) => {
    await page.fill('input[name="email"]', 'user@example.com');
    await page.fill('input[name="password"]', 'password123');
    await page.click('button[type="submit"]');
    
    await expect(page).toHaveURL('http://localhost:3000/dashboard');
    await expect(page.locator('h1')).toContainText('Dashboard');
  });
  
  test('should show error with invalid credentials', async ({ page }) => {
    await page.fill('input[name="email"]', 'wrong@example.com');
    await page.fill('input[name="password"]', 'wrongpassword');
    await page.click('button[type="submit"]');
    
    await expect(page.locator('.error')).toContainText('Invalid credentials');
  });
  
  test('should validate email format', async ({ page }) => {
    await page.fill('input[name="email"]', 'invalid-email');
    await page.fill('input[name="password"]', 'password123');
    await page.click('button[type="submit"]');
    
    const emailInput = page.locator('input[name="email"]');
    await expect(emailInput).toHaveAttribute('aria-invalid', 'true');
  });
});


// Test avec authentification
test.describe('Authenticated User', () => {
  test.use({ storageState: 'auth.json' }); // État authentifié
  
  test('should access protected page', async ({ page }) => {
    await page.goto('http://localhost:3000/profile');
    await expect(page.locator('h1')).toContainText('My Profile');
  });
});


// Générer état authentifié
import { chromium } from '@playwright/test';

async function globalSetup() {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  
  await page.goto('http://localhost:3000/login');
  await page.fill('input[name="email"]', 'user@example.com');
  await page.fill('input[name="password"]', 'password123');
  await page.click('button[type="submit"]');
  
  await page.waitForURL('http://localhost:3000/dashboard');
  
  // Sauvegarder état authentifié
  await page.context().storageState({ path: 'auth.json' });
  
  await browser.close();
}

export default globalSetup;


# === Visual Regression Testing ===

// Avec Playwright screenshots
test('homepage looks correct', async ({ page }) => {
  await page.goto('http://localhost:3000');
  await expect(page).toHaveScreenshot('homepage.png');
});


// Avec Percy
// npm install --save-dev @percy/playwright

import { test } from '@playwright/test';
import percySnapshot from '@percy/playwright';

test('visual test homepage', async ({ page }) => {
  await page.goto('http://localhost:3000');
  await percySnapshot(page, 'Homepage');
});


# === Component Testing (Storybook + Testing) ===

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs']
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    variant: 'primary',
    children: 'Click me'
  }
};

export const Secondary: Story = {
  args: {
    variant: 'secondary',
    children: 'Click me'
  }
};

export const Disabled: Story = {
  args: {
    disabled: true,
    children: 'Disabled'
  }
};


// Test de stories
import { composeStories } from '@storybook/react';
import { render, screen } from '@testing-library/react';
import * as stories from './Button.stories';

const { Primary, Disabled } = composeStories(stories);

test('renders primary button', () => {
  render(<Primary />);
  expect(screen.getByRole('button')).toHaveClass('primary');
});

test('disabled button is not clickable', () => {
  const onClick = jest.fn();
  render(<Disabled onClick={onClick} />);
  
  const button = screen.getByRole('button');
  expect(button).toBeDisabled();
});


# === Performance Testing ===

// Mesurer temps de rendu
import { render } from '@testing-library/react';

test('component renders within performance budget', () => {
  const startTime = performance.now();
  
  render(<HeavyComponent />);
  
  const endTime = performance.now();
  const renderTime = endTime - startTime;
  
  expect(renderTime).toBeLessThan(16); // <16ms pour 60fps
});


// Avec React Testing Library + performance
import { renderHook } from '@testing-library/react';

test('custom hook performance', () => {
  const { result, rerender } = renderHook(
    ({ count }) => useExpensiveCalculation(count),
    { initialProps: { count: 0 } }
  );
  
  const startTime = performance.now();
  
  for (let i = 0; i < 100; i++) {
    rerender({ count: i });
  }
  
  const endTime = performance.now();
  const totalTime = endTime - startTime;
  
  expect(totalTime).toBeLessThan(100); // 100 updates en <100ms
});


# === Accessibility Testing ===

// Avec jest-axe
// npm install --save-dev jest-axe

import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

test('should not have accessibility violations', async () => {
  const { container } = render(<LoginForm />);
  const results = await axe(container);
  
  expect(results).toHaveNoViolations();
});


// Test spécifiques d'accessibilité
test('form has proper labels', () => {
  render(<LoginForm />);
  
  const emailInput = screen.getByLabelText(/email/i);
  const passwordInput = screen.getByLabelText(/password/i);
  
  expect(emailInput).toBeInTheDocument();
  expect(passwordInput).toBeInTheDocument();
});

test('buttons have accessible names', () => {
  render(<IconButton icon={<CloseIcon />} />);
  
  const button = screen.getByRole('button', { name: /close/i });
  expect(button).toBeInTheDocument();
});


# === Snapshot Testing ===

import renderer from 'react-test-renderer';

test('Button matches snapshot', () => {
  const tree = renderer
    .create(<Button variant="primary">Click me</Button>)
    .toJSON();
  
  expect(tree).toMatchSnapshot();
});


// Avec serializers personnalisés
test('Component with dynamic data', () => {
  const tree = renderer
    .create(<UserCard user={{ id: 1, name: 'John', timestamp: Date.now() }} />)
    .toJSON();
  
  expect(tree).toMatchSnapshot({
    props: {
      user: {
        timestamp: expect.any(Number) // Ignorer timestamp dynamique
      }
    }
  });
});



[OK] DÉPLOIEMENT ET CI/CD


# === GitHub Actions ===

// .github/workflows/ci.yml
name: CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Lint
        run: npm run lint
      
      - name: Type check
        run: npm run type-check
      
      - name: Run tests
        run: npm test -- --coverage
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/coverage-final.json
  
  build:
    needs: test
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
        env:
          REACT_APP_API_URL: ${{ secrets.API_URL }}
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build
          path: build/
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: build
      
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID }}
          vercel-project-id: ${{ secrets.PROJECT_ID }}
          vercel-args: '--prod'


# === Docker Multi-stage Build ===

# Dockerfile
# Stage 1: Build
FROM node:18-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# Stage 2: Production
FROM nginx:alpine

COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]


# nginx.conf
server {
    listen 80;
    server_name _;
    
    root /usr/share/nginx/html;
    index index.html;
    
    # Compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    
    # Cache static assets
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # SPA fallback
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # Security headers
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
}


# docker-compose.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "80:80"
    environment:
      - NODE_ENV=production
    restart: unless-stopped


# === Kubernetes Deployment ===

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: react-app
  labels:
    app: react-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: react-app
  template:
    metadata:
      labels:
        app: react-app
    spec:
      containers:
      - name: react-app
        image: myregistry/react-app:latest
        ports:
        - containerPort: 80
        env:
        - name: NODE_ENV
          value: "production"
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: react-app-service
spec:
  type: LoadBalancer
  selector:
    app: react-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: react-app-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - myapp.com
    secretName: react-app-tls
  rules:
  - host: myapp.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: react-app-service
            port:
              number: 80


# === Feature Flags ===

// Avec LaunchDarkly, Split.io, ou custom solution

// Simple feature flag hook
function useFeatureFlag(flagName: string): boolean {
  const [isEnabled, setIsEnabled] = useState(false);
  
  useEffect(() => {
    // Fetch depuis API ou configuration
    fetch(`/api/features/${flagName}`)
      .then(res => res.json())
      .then(data => setIsEnabled(data.enabled))
      .catch(() => setIsEnabled(false));
  }, [flagName]);
  
  return isEnabled;
}


// Utilisation
function App() {
  const showNewFeature = useFeatureFlag('new-dashboard');
  
  return (
    <div>
      {showNewFeature ? <NewDashboard /> : <OldDashboard />}
    </div>
  );
}


// Context pour feature flags
interface FeatureFlagsContextType {
  flags: Record<string, boolean>;
  isEnabled: (flag: string) => boolean;
}

const FeatureFlagsContext = createContext<FeatureFlagsContextType | null>(null);

export function FeatureFlagsProvider({ children }: { children: ReactNode }) {
  const [flags, setFlags] = useState<Record<string, boolean>>({});
  
  useEffect(() => {
    fetch('/api/feature-flags')
      .then(res => res.json())
      .then(setFlags);
  }, []);
  
  const isEnabled = useCallback((flag: string) => {
    return flags[flag] || false;
  }, [flags]);
  
  return (
    <FeatureFlagsContext.Provider value={{ flags, isEnabled }}>
      {children}
    </FeatureFlagsContext.Provider>
  );
}


# === A/B Testing ===

function useABTest(testName: string, variants: string[]): string {
  const [variant, setVariant] = useState(variants[0]);
  
  useEffect(() => {
    // Assigner variant basé sur user ID ou random
    const userId = getUserId();
    const hash = hashCode(userId + testName);
    const variantIndex = Math.abs(hash) % variants.length;
    
    setVariant(variants[variantIndex]);
    
    // Log vers analytics
    analytics.track('AB_Test_Assigned', {
      testName,
      variant: variants[variantIndex]
    });
  }, [testName, variants]);
  
  return variant;
}

// Utilisation
function PricingPage() {
  const buttonVariant = useABTest('pricing-button', ['blue', 'green', 'red']);
  
  return (
    <div>
      <h1>Pricing</h1>
      <button className={`btn-${buttonVariant}`}>
        Subscribe Now
      </button>
    </div>
  );
}



[OK] ANALYTICS & MONITORING


# === Google Analytics 4 ===

// Wrapper pour GA4
class Analytics {
  static init(measurementId: string) {
    if (typeof window === 'undefined') return;
    
    // Charger gtag.js
    const script = document.createElement('script');
    script.async = true;
    script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
    document.head.appendChild(script);
    
    window.dataLayer = window.dataLayer || [];
    function gtag(...args: any[]) {
      window.dataLayer.push(args);
    }
    
    gtag('js', new Date());
    gtag('config', measurementId);
    
    window.gtag = gtag;
  }
  
  static pageView(url: string, title: string) {
    window.gtag?.('event', 'page_view', {
      page_path: url,
      page_title: title
    });
  }
  
  static event(eventName: string, params?: Record<string, any>) {
    window.gtag?.('event', eventName, params);
  }
  
  static setUser(userId: string) {
    window.gtag?.('set', { user_id: userId });
  }
}


// Hook pour tracking automatique
function usePageTracking() {
  const location = useLocation();
  
  useEffect(() => {
    Analytics.pageView(location.pathname, document.title);
  }, [location]);
}


// Hook pour événements
function useAnalytics() {
  const trackEvent = useCallback((eventName: string, params?: Record<string, any>) => {
    Analytics.event(eventName, params);
  }, []);
  
  const trackClick = useCallback((elementName: string) => {
    Analytics.event('click', { element: elementName });
  }, []);
  
  const trackFormSubmit = useCallback((formName: string) => {
    Analytics.event('form_submit', { form_name: formName });
  }, []);
  
  return { trackEvent, trackClick, trackFormSubmit };
}


// Utilisation
function ProductCard({ product }: { product: Product }) {
  const { trackClick } = useAnalytics();
  
  const handleAddToCart = () => {
    trackClick('add_to_cart');
    Analytics.event('add_to_cart', {
      currency: 'USD',
      value: product.price,
      items: [{
        item_id: product.id,
        item_name: product.name,
        price: product.price
      }]
    });
    
    // Logique d'ajout au panier...
  };
  
  return (
    <div>
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button onClick={handleAddToCart}>Add to Cart</button>
    </div>
  );
}


# === Error Monitoring avec Sentry ===

// npm install @sentry/react

import * as Sentry from '@sentry/react';

Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  environment: process.env.NODE_ENV,
  integrations: [
    new Sentry.BrowserTracing(),
    new Sentry.Replay({
      maskAllText: true,
      blockAllMedia: true
    })
  ],
  
  // Performance Monitoring
  tracesSampleRate: 1.0, // 100% en dev, réduire en prod
  
  // Session Replay
  replaysSessionSampleRate: 0.1, // 10%
  replaysOnErrorSampleRate: 1.0, // 100% si erreur
  
  beforeSend(event, hint) {
    // Filtrer erreurs non importantes
    if (event.exception) {
      const error = hint.originalException;
      if (error && error.message.match(/ResizeObserver/i)) {
        return null; // Ignorer cette erreur
      }
    }
    return event;
  }
});


// ErrorBoundary avec Sentry
import { ErrorBoundary as SentryErrorBoundary } from '@sentry/react';

function App() {
  return (
    <SentryErrorBoundary
      fallback={({ error, componentStack, resetError }) => (
        <div>
          <h1>Something went wrong</h1>
          <p>{error.toString()}</p>
          <button onClick={resetError}>Try again</button>
        </div>
      )}
      showDialog
    >
      <MainApp />
    </SentryErrorBoundary>
  );
}


// Tracking manuel d'erreurs
try {
  riskyOperation();
} catch (error) {
  Sentry.captureException(error, {
    tags: {
      section: 'checkout'
    },
    extra: {
      userId: user.id,
      cartValue: cart.total
    }
  });
}


// Breadcrumbs (fil d'Ariane)
Sentry.addBreadcrumb({
  category: 'auth',
  message: 'User logged in',
  level: 'info'
});


// Profiler de performance
import { Profiler } from '@sentry/react';

function App() {
  return (
    <Profiler name="App">
      <MainContent />
    </Profiler>
  );
}


# === Custom Monitoring Dashboard ===

// Hook pour collecter métriques
function useMetrics() {
  const [metrics, setMetrics] = useState({
    renders: 0,
    errors: 0,
    apiCalls: 0,
    slowRenders: 0
  });
  
  const incrementMetric = useCallback((metric: keyof typeof metrics) => {
    setMetrics(prev => ({
      ...prev,
      [metric]: prev[metric] + 1
    }));
  }, []);
  
  // Envoyer périodiquement au backend
  useEffect(() => {
    const interval = setInterval(() => {
      if (Object.values(metrics).some(v => v > 0)) {
        fetch('/api/metrics', {
          method: 'POST',
          body: JSON.stringify({
            ...metrics,
            timestamp: Date.now(),
            userId: getUserId(),
            sessionId: getSessionId()
          })
        });
        
        // Reset après envoi
        setMetrics({
          renders: 0,
          errors: 0,
          apiCalls: 0,
          slowRenders: 0
        });
      }
    }, 60000); // Chaque minute
    
    return () => clearInterval(interval);
  }, [metrics]);
  
  return { metrics, incrementMetric };
}


// Context pour métriques globales
const MetricsContext = createContext<ReturnType<typeof useMetrics> | null>(null);

export function MetricsProvider({ children }: { children: ReactNode }) {
  const metricsValue = useMetrics();
  
  return (
    <MetricsContext.Provider value={metricsValue}>
      {children}
    </MetricsContext.Provider>
  );
}

export function useMetricsContext() {
  const context = useContext(MetricsContext);
  if (!context) {
    throw new Error('useMetricsContext must be used within MetricsProvider');
  }
  return context;
}


# === Real User Monitoring (RUM) ===

function useRUM() {
  useEffect(() => {
    // Navigation Timing
    const perfData = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
    
    if (perfData) {
      const metrics = {
        dns: perfData.domainLookupEnd - perfData.domainLookupStart,
        tcp: perfData.connectEnd - perfData.connectStart,
        ttfb: perfData.responseStart - perfData.requestStart,
        download: perfData.responseEnd - perfData.responseStart,
        domInteractive: perfData.domInteractive - perfData.fetchStart,
        domComplete: perfData.domComplete - perfData.fetchStart,
        loadComplete: perfData.loadEventEnd - perfData.fetchStart
      };
      
      // Envoyer métriques
      fetch('/api/rum', {
        method: 'POST',
        body: JSON.stringify({
          type: 'navigation',
          metrics,
          url: window.location.href,
          userAgent: navigator.userAgent,
          timestamp: Date.now()
        })
      });
    }
    
    // Resource Timing
    const resources = performance.getEntriesByType('resource');
    const slowResources = resources.filter(r => r.duration > 1000);
    
    if (slowResources.length > 0) {
      fetch('/api/rum', {
        method: 'POST',
        body: JSON.stringify({
          type: 'slow_resources',
          resources: slowResources.map(r => ({
            name: r.name,
            duration: r.duration,
            size: (r as PerformanceResourceTiming).transferSize
          }))
        })
      });
    }
  }, []);
}


# === Session Recording (privacy-safe) ===

// Simple session recorder (événements seulement, pas de DOM)
class SessionRecorder {
  private events: Array<{
    type: string;
    timestamp: number;
    data: any;
  }> = [];
  
  private sessionId: string;
  
  constructor() {
    this.sessionId = crypto.randomUUID();
    this.setupListeners();
  }
  
  private setupListeners() {
    // Clics (sans contenu sensible)
    document.addEventListener('click', (e) => {
      const target = e.target as HTMLElement;
      this.recordEvent('click', {
        tagName: target.tagName,
        id: target.id,
        className: target.className,
        x: e.clientX,
        y: e.clientY
      });
    });
    
    // Navigation
    window.addEventListener('popstate', () => {
      this.recordEvent('navigation', {
        url: window.location.href
      });
    });
    
    // Erreurs
    window.addEventListener('error', (e) => {
      this.recordEvent('error', {
        message: e.message,
        filename: e.filename,
        lineno: e.lineno,
        colno: e.colno
      });
    });
  }
  
  private recordEvent(type: string, data: any) {
    this.events.push({
      type,
      timestamp: Date.now(),
      data
    });
    
    // Envoyer batch si > 50 événements
    if (this.events.length >= 50) {
      this.flush();
    }
  }
  
  flush() {
    if (this.events.length === 0) return;
    
    fetch('/api/session-recording', {
      method: 'POST',
      body: JSON.stringify({
        sessionId: this.sessionId,
        events: this.events
      })
    });
    
    this.events = [];
  }
}


// Hook pour session recording
function useSessionRecording() {
  useEffect(() => {
    const recorder = new SessionRecorder();
    
    // Flush avant fermeture
    const handleBeforeUnload = () => {
      recorder.flush();
    };
    
    window.addEventListener('beforeunload', handleBeforeUnload);
    
    return () => {
      window.removeEventListener('beforeunload', handleBeforeUnload);
      recorder.flush();
    };
  }, []);
}



[WEB] SEO & META TAGS


# === React Helmet (Gestion des meta tags) ===

// npm install react-helmet-async

import { Helmet, HelmetProvider } from 'react-helmet-async';

function App() {
  return (
    <HelmetProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/blog/:slug" element={<BlogPost />} />
        </Routes>
      </Router>
    </HelmetProvider>
  );
}


// Composant avec SEO
function BlogPost({ post }: { post: Post }) {
  return (
    <>
      <Helmet>
        <title>{post.title} | My Blog</title>
        <meta name="description" content={post.excerpt} />
        
        {/* Open Graph */}
        <meta property="og:type" content="article" />
        <meta property="og:title" content={post.title} />
        <meta property="og:description" content={post.excerpt} />
        <meta property="og:image" content={post.coverImage} />
        <meta property="og:url" content={`https://myblog.com/blog/${post.slug}`} />
        
        {/* Twitter Card */}
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:title" content={post.title} />
        <meta name="twitter:description" content={post.excerpt} />
        <meta name="twitter:image" content={post.coverImage} />
        
        {/* Article metadata */}
        <meta property="article:published_time" content={post.publishedAt} />
        <meta property="article:author" content={post.author.name} />
        <meta property="article:tag" content={post.tags.join(', ')} />
        
        {/* Canonical */}
        <link rel="canonical" href={`https://myblog.com/blog/${post.slug}`} />
        
        {/* JSON-LD Structured Data */}
        <script type="application/ld+json">
          {JSON.stringify({
            '@context': 'https://schema.org',
            '@type': 'BlogPosting',
            headline: post.title,
            description: post.excerpt,
            image: post.coverImage,
            datePublished: post.publishedAt,
            dateModified: post.updatedAt,
            author: {
              '@type': 'Person',
              name: post.author.name
            }
          })}
        </script>
      </Helmet>
      
      <article>
        <h1>{post.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: post.content }} />
      </article>
    </>
  );
}


// Hook personnalisé pour SEO
interface SEOProps {
  title: string;
  description: string;
  image?: string;
  url?: string;
  type?: string;
}

function useSEO({ title, description, image, url, type = 'website' }: SEOProps) {
  const fullTitle = `${title} | My Website`;
  const fullUrl = url || window.location.href;
  const ogImage = image || '/default-og-image.jpg';
  
  return (
    <Helmet>
      <title>{fullTitle}</title>
      <meta name="description" content={description} />
      
      <meta property="og:type" content={type} />
      <meta property="og:title" content={fullTitle} />
      <meta property="og:description" content={description} />
      <meta property="og:image" content={ogImage} />
      <meta property="og:url" content={fullUrl} />
      
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:title" content={fullTitle} />
      <meta name="twitter:description" content={description} />
      <meta name="twitter:image" content={ogImage} />
      
      <link rel="canonical" href={fullUrl} />
    </Helmet>
  );
}


// Utilisation
function ProductPage({ product }: { product: Product }) {
  const seo = useSEO({
    title: product.name,
    description: product.description,
    image: product.images[0],
    url: `https://mystore.com/products/${product.slug}`,
    type: 'product'
  });
  
  return (
    <>
      {seo}
      <div>
        <h1>{product.name}</h1>
        <p>{product.description}</p>
      </div>
    </>
  );
}


# === Sitemap Generation ===

// scripts/generate-sitemap.js
const fs = require('fs');
const path = require('path');

async function generateSitemap() {
  const baseUrl = 'https://mywebsite.com';
  
  // Récupérer toutes les pages dynamiques
  const posts = await fetch('https://api.mywebsite.com/posts').then(r => r.json());
  const products = await fetch('https://api.mywebsite.com/products').then(r => r.json());
  
  const staticPages = [
    { url: '/', priority: 1.0, changefreq: 'daily' },
    { url: '/about', priority: 0.8, changefreq: 'monthly' },
    { url: '/contact', priority: 0.7, changefreq: 'monthly' }
  ];
  
  const dynamicPages = [
    ...posts.map(post => ({
      url: `/blog/${post.slug}`,
      priority: 0.9,
      changefreq: 'weekly',
      lastmod: post.updatedAt
    })),
    ...products.map(product => ({
      url: `/products/${product.slug}`,
      priority: 0.9,
      changefreq: 'weekly',
      lastmod: product.updatedAt
    }))
  ];
  
  const allPages = [...staticPages, ...dynamicPages];
  
  const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  ${allPages.map(page => `
  <url>
    <loc>${baseUrl}${page.url}</loc>
    ${page.lastmod ? `<lastmod>${page.lastmod}</lastmod>` : ''}
    <changefreq>${page.changefreq}</changefreq>
    <priority>${page.priority}</priority>
  </url>`).join('')}
</urlset>`;
  
  fs.writeFileSync(
    path.join(__dirname, '../public/sitemap.xml'),
    sitemap
  );
  
  console.log('Sitemap generated successfully!');
}

generateSitemap();


// package.json
{
  "scripts": {
    "generate-sitemap": "node scripts/generate-sitemap.js",
    "prebuild": "npm run generate-sitemap"
  }
}


# === Robots.txt ===

// public/robots.txt
User-agent: *
Allow: /

Sitemap: https://mywebsite.com/sitemap.xml

# Bloquer certaines pages
Disallow: /admin/
Disallow: /api/
Disallow: /private/


# === Structured Data (Schema.org) ===

// Composant pour structured data
function StructuredData({ data }: { data: object }) {
  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
    />
  );
}


// Exemples de schemas

// Organization
const organizationSchema = {
  '@context': 'https://schema.org',
  '@type': 'Organization',
  name: 'My Company',
  url: 'https://mycompany.com',
  logo: 'https://mycompany.com/logo.png',
  contactPoint: {
    '@type': 'ContactPoint',
    telephone: '+1-555-555-5555',
    contactType: 'customer service'
  },
  sameAs: [
    'https://facebook.com/mycompany',
    'https://twitter.com/mycompany',
    'https://linkedin.com/company/mycompany'
  ]
};


// Product
const productSchema = {
  '@context': 'https://schema.org',
  '@type': 'Product',
  name: product.name,
  image: product.images,
  description: product.description,
  sku: product.sku,
  brand: {
    '@type': 'Brand',
    name: product.brand
  },
  offers: {
    '@type': 'Offer',
    url: `https://mystore.com/products/${product.slug}`,
    priceCurrency: 'USD',
    price: product.price,
    availability: product.inStock 
      ? 'https://schema.org/InStock' 
      : 'https://schema.org/OutOfStock'
  },
  aggregateRating: {
    '@type': 'AggregateRating',
    ratingValue: product.rating,
    reviewCount: product.reviewCount
  }
};


// Breadcrumb
const breadcrumbSchema = {
  '@context': 'https://schema.org',
  '@type': 'BreadcrumbList',
  itemListElement: [
    {
      '@type': 'ListItem',
      position: 1,
      name: 'Home',
      item: 'https://mysite.com'
    },
    {
      '@type': 'ListItem',
      position: 2,
      name: 'Products',
      item: 'https://mysite.com/products'
    },
    {
      '@type': 'ListItem',
      position: 3,
      name: product.name,
      item: `https://mysite.com/products/${product.slug}`
    }
  ]
};


// FAQ
const faqSchema = {
  '@context': 'https://schema.org',
  '@type': 'FAQPage',
  mainEntity: faqs.map(faq => ({
    '@type': 'Question',
    name: faq.question,
    acceptedAnswer: {
      '@type': 'Answer',
      text: faq.answer
    }
  }))
};



[OK] RESSOURCES SUPPLÉMENTAIRES


# === Documentation officielle ===

React Docs (nouvelle): https://react.dev/
React API Reference: https://react.dev/reference/react
React 19 Beta: https://react.dev/blog/2024/04/25/react-19


# === Frameworks React ===

Next.js: https://nextjs.org/
Remix: https://remix.run/
Gatsby: https://www.gatsbyjs.com/
Astro: https://astro.build/ (partiellement React)


# === Bibliothèques State Management ===

Redux Toolkit: https://redux-toolkit.js.org/
Zustand: https://github.com/pmndrs/zustand
Jotai: https://jotai.org/
Recoil: https://recoiljs.org/
MobX: https://mobx.js.org/
XState: https://xstate.js.org/


# === Data Fetching ===

TanStack Query: https://tanstack.com/query
SWR: https://swr.vercel.app/
Apollo Client: https://www.apollographql.com/docs/react/
tRPC: https://trpc.io/


# === UI Component Libraries ===

Material-UI (MUI): https://mui.com/
Ant Design: https://ant.design/
Chakra UI: https://chakra-ui.com/
Mantine: https://mantine.dev/
shadcn/ui: https://ui.shadcn.com/
Radix UI: https://www.radix-ui.com/
Headless UI: https://headlessui.com/


# === Animation ===

Framer Motion: https://www.framer.com/motion/
React Spring: https://react-spring.dev/
GSAP: https://greensock.com/react/
React Transition Group: https://reactcommunity.org/react-transition-group/


# === Formulaires ===

React Hook Form: https://react-hook-form.com/
Formik: https://formik.org/
React Final Form: https://final-form.org/react


# === Testing ===

React Testing Library: https://testing-library.com/react
Jest: https://jestjs.io/
Vitest: https://vitest.dev/
Cypress: https://www.cypress.io/
Playwright: https://playwright.dev/
Storybook: https://storybook.js.org/


# === Build Tools ===

Vite: https://vitejs.dev/
Webpack: https://webpack.js.org/
Turbopack: https://turbo.build/pack
esbuild: https://esbuild.github.io/


# === Styling ===

Tailwind CSS: https://tailwindcss.com/
Styled Components: https://styled-components.com/
Emotion: https://emotion.sh/
CSS Modules: https://github.com/css-modules/css-modules
Stitches: https://stitches.dev/
Panda CSS: https://panda-css.com/


# === TypeScript ===

TypeScript Handbook: https://www.typescriptlang.org/docs/
React TypeScript Cheatsheet: https://react-typescript-cheatsheet.netlify.app/


# === Performance ===

React DevTools: https://react.dev/learn/react-developer-tools
Lighthouse: https://developers.google.com/web/tools/lighthouse
Web Vitals: https://web.dev/vitals/
Bundle Analyzer: https://www.npmjs.com/package/webpack-bundle-analyzer


# === Monitoring & Analytics ===

Sentry: https://sentry.io/
LogRocket: https://logrocket.com/
Datadog: https://www.datadoghq.com/
New Relic: https://newrelic.com/


# === Cours et Tutoriels ===

React Official Tutorial: https://react.dev/learn
freeCodeCamp React: https://www.freecodecamp.org/learn/front-end-libraries/react/
Epic React by Kent C. Dodds: https://epicreact.dev/
React Patterns: https://reactpatterns.com/


# === Communautés ===

React Discord: https://discord.gg/react
Reddit r/reactjs: https://reddit.com/r/reactjs
Stack Overflow: https://stackoverflow.com/questions/tagged/reactjs
DEV.to React: https://dev.to/t/react


# === Blogs et Newsletters ===

React Blog: https://react.dev/blog
Overreacted (Dan Abramov): https://overreacted.io/
Kent C. Dodds Blog: https://kentcdodds.com/blog
Josh Comeau: https://www.joshwcomeau.com/
React Status Newsletter: https://react.statuscode.com/


# === GitHub Repositories ===

Awesome React: https://github.com/enaqx/awesome-react
React Bits: https://github.com/vasanthk/react-bits
React Patterns: https://github.com/reactpatterns/reactpatterns.com



[OK] AIDE-MÉMOIRE RAPIDE - FONCTIONNALITÉS AVANCÉES


# React Server Components (RSC)
- Composants serveur: async, accès DB direct, pas de JS client
- Composants client: marqués 'use client', interactivité, hooks
- Streaming avec <Suspense>
- Server Actions: mutations côté serveur

# React 19
use()               -> Lire Promises/Context (même conditionnellement)
useFormStatus()     -> Statut soumission formulaire
useOptimistic()     -> Mises à jour UI optimistes
useActionState()    -> Gérer état actions async
ref as prop         -> Plus besoin de forwardRef

# Web Workers
- Calculs intensifs en arrière-plan
- Communication via postMessage
- Comlink pour RPC simplifié
- Worker Pool pour parallélisme

# Micro-frontends
- Module Federation (Webpack 5)
- Web Components
- Single-SPA
- Iframe-based

# Animations
- Framer Motion: déclaratif, layout animations
- React Spring: physique
- GSAP: timeline, ScrollTrigger

# i18n
- react-i18next
- Pluralisation, interpolation
- Namespaces, lazy loading
- Formatage dates/nombres

# PWA
- Service Worker (cache strategies)
- manifest.json
- Offline support
- Push notifications
- Install prompt

# Performance
- Web Vitals (CLS, FID, LCP, etc.)
- React Profiler
- Memory monitoring
- Long tasks detection

# Architecture
- Feature-Sliced Design
- Clean Architecture
- Repository Pattern
- Dependency Injection

# Sécurité
- CSP headers
- XSS protection (DOMPurify)
- CSRF tokens
- JWT refresh
- Input validation (Zod)

# Testing
- MSW pour mocking API
- Playwright E2E
- Visual regression
- Accessibility (jest-axe)
- Performance tests

# CI/CD
- GitHub Actions
- Docker multi-stage
- Kubernetes
- Feature flags
- A/B testing

# Monitoring
- Sentry (errors)
- Google Analytics 4
- RUM metrics
- Session recording
- Custom dashboards

# SEO
- React Helmet
- Meta tags (OG, Twitter)
- Sitemap generation
- Structured data (Schema.org)
- robots.txt


[OK] FORMULAIRES


# === Composants contrôlés ===

function ControlledForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: 0,
    country: 'fr',
    terms: false
  });
  
  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form data:', formData);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="name"
        value={formData.name}
        onChange={handleChange}
      />
      
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
      />
      
      <input
        type="number"
        name="age"
        value={formData.age}
        onChange={handleChange}
      />
      
      <select name="country" value={formData.country} onChange={handleChange}>
        <option value="fr">France</option>
        <option value="us">USA</option>
        <option value="uk">UK</option>
      </select>
      
      <label>
        <input
          type="checkbox"
          name="terms"
          checked={formData.terms}
          onChange={handleChange}
        />
        Accept terms
      </label>
      
      <button type="submit">Submit</button>
    </form>
  );
}


# === Composants non-contrôlés (avec refs) ===

function UncontrolledForm() {
  const nameRef = useRef(null);
  const emailRef = useRef(null);
  const fileRef = useRef(null);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    const data = {
      name: nameRef.current.value,
      email: emailRef.current.value,
      file: fileRef.current.files[0]
    };
    
    console.log('Form data:', data);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" ref={nameRef} defaultValue="John" />
      <input type="email" ref={emailRef} />
      <input type="file" ref={fileRef} />
      <button type="submit">Submit</button>
    </form>
  );
}


# === Validation ===

function FormWithValidation() {
  const [values, setValues] = useState({ email: '', password: '' });
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  
  const validate = (name, value) => {
    switch (name) {
      case 'email':
        if (!value) return 'Email is required';
        if (!/\S+@\S+\.\S+/.test(value)) return 'Email is invalid';
        return '';
      case 'password':
        if (!value) return 'Password is required';
        if (value.length < 8) return 'Password must be 8+ characters';
        return '';
      default:
        return '';
    }
  };
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
    
    const error = validate(name, value);
    setErrors(prev => ({ ...prev, [name]: error }));
  };
  
  const handleBlur = (e) => {
    const { name } = e.target;
    setTouched(prev => ({ ...prev, [name]: true }));
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    // Valider tous les champs
    const newErrors = {};
    Object.keys(values).forEach(key => {
      const error = validate(key, values[key]);
      if (error) newErrors[key] = error;
    });
    
    setErrors(newErrors);
    setTouched({ email: true, password: true });
    
    if (Object.keys(newErrors).length === 0) {
      console.log('Form valid:', values);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          type="email"
          name="email"
          value={values.email}
          onChange={handleChange}
          onBlur={handleBlur}
        />
        {touched.email && errors.email && (
          <span className="error">{errors.email}</span>
        )}
      </div>
      
      <div>
        <input
          type="password"
          name="password"
          value={values.password}
          onChange={handleChange}
          onBlur={handleBlur}
        />
        {touched.password && errors.password && (
          <span className="error">{errors.password}</span>
        )}
      </div>
      
      <button type="submit">Submit</button>
    </form>
  );
}


# === Hook formulaire personnalisé ===

function useForm(initialValues, validate) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
    
    if (validate) {
      const fieldErrors = validate({ ...values, [name]: value });
      setErrors(prev => ({ ...prev, [name]: fieldErrors[name] }));
    }
  };
  
  const handleBlur = (e) => {
    const { name } = e.target;
    setTouched(prev => ({ ...prev, [name]: true }));
  };
  
  const handleSubmit = async (onSubmit) => async (e) => {
    e.preventDefault();
    
    setIsSubmitting(true);
    
    if (validate) {
      const formErrors = validate(values);
      setErrors(formErrors);
      
      if (Object.keys(formErrors).length === 0) {
        await onSubmit(values);
      }
    } else {
      await onSubmit(values);
    }
    
    setIsSubmitting(false);
  };
  
  const reset = () => {
    setValues(initialValues);
    setErrors({});
    setTouched({});
    setIsSubmitting(false);
  };
  
  return {
    values,
    errors,
    touched,
    isSubmitting,
    handleChange,
    handleBlur,
    handleSubmit,
    reset
  };
}

// Utilisation
function LoginForm() {
  const validate = (values) => {
    const errors = {};
    if (!values.email) errors.email = 'Required';
    if (!values.password) errors.password = 'Required';
    return errors;
  };
  
  const {
    values,
    errors,
    touched,
    isSubmitting,
    handleChange,
    handleBlur,
    handleSubmit
  } = useForm({ email: '', password: '' }, validate);
  
  const onSubmit = async (values) => {
    await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(values)
    });
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        name="email"
        value={values.email}
        onChange={handleChange}
        onBlur={handleBlur}
      />
      {touched.email && errors.email && <span>{errors.email}</span>}
      
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Loading...' : 'Submit'}
      </button>
    </form>
  );
}


[OK] ROUTING (React Router v6)


# === Configuration basique ===

import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/users">Users</Link>
      </nav>
      
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users" element={<Users />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}


# === Routes imbriquées ===

function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="users" element={<Users />}>
          <Route index element={<UserList />} />
          <Route path=":userId" element={<UserProfile />} />
          <Route path="new" element={<NewUser />} />
        </Route>
      </Route>
    </Routes>
  );
}

// Layout avec Outlet
import { Outlet } from 'react-router-dom';

function Layout() {
  return (
    <div>
      <nav>{/* Navigation */}</nav>
      <main>
        <Outlet /> {/* Composants enfants rendus ici */}
      </main>
      <footer>{/* Footer */}</footer>
    </div>
  );
}


# === Paramètres de route ===

import { useParams } from 'react-router-dom';

// Route: /users/:userId
function UserProfile() {
  const { userId } = useParams();
  
  return <div>User ID: {userId}</div>;
}

// Paramètres multiples: /posts/:postId/comments/:commentId
function Comment() {
  const { postId, commentId } = useParams();
  
  return <div>Post {postId}, Comment {commentId}</div>;
}


# === Query parameters ===

import { useSearchParams } from 'react-router-dom';

function SearchResults() {
  const [searchParams, setSearchParams] = useSearchParams();
  
  const query = searchParams.get('q');
  const page = searchParams.get('page') || '1';
  
  const handleSearch = (newQuery) => {
    setSearchParams({ q: newQuery, page: '1' });
  };
  
  return (
    <div>
      <p>Query: {query}</p>
      <p>Page: {page}</p>
    </div>
  );
}


# === Navigation programmatique ===

import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const navigate = useNavigate();
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    await login();
    
    // Navigation simple
    navigate('/dashboard');
    
    // Avec replace (pas d'historique)
    navigate('/dashboard', { replace: true });
    
    // Navigation relative
    navigate('../users');
    
    // Retour arrière
    navigate(-1);
    
    // Avec state
    navigate('/success', { state: { from: 'login' } });
  };
  
  return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}


# === Protected routes ===

function ProtectedRoute({ children }) {
  const { user } = useAuth();
  
  if (!user) {
    return <Navigate to="/login" replace />;
  }
  
  return children;
}

// Utilisation
<Route
  path="/dashboard"
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  }
/>


# === useLocation ===

import { useLocation } from 'react-router-dom';

function Component() {
  const location = useLocation();
  
  console.log(location.pathname);   // "/users/123"
  console.log(location.search);     // "?sort=name"
  console.log(location.hash);       // "#section"
  console.log(location.state);      // State passé via navigate
  
  return <div>Current path: {location.pathname}</div>;
}


# === Link actif ===

import { NavLink } from 'react-router-dom';

function Navigation() {
  return (
    <nav>
      <NavLink
        to="/"
        className={({ isActive }) => isActive ? 'active' : ''}
      >
        Home
      </NavLink>
      
      <NavLink
        to="/about"
        style={({ isActive }) => ({
          color: isActive ? 'red' : 'blue'
        })}
      >
        About
      </NavLink>
    </nav>
  );
}


# === Lazy loading routes ===

import { lazy, Suspense } from 'react';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Users = lazy(() => import('./pages/Users'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<Spinner />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/users" element={<Users />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}


[OK] STATE MANAGEMENT


# === Context API (léger) ===

// Store simple
const AppContext = createContext();

function AppProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [notifications, setNotifications] = useState([]);
  
  const login = async (credentials) => {
    const user = await authAPI.login(credentials);
    setUser(user);
  };
  
  const logout = () => {
    setUser(null);
  };
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  const value = {
    user,
    theme,
    notifications,
    login,
    logout,
    toggleTheme
  };
  
  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}

function useApp() {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error('useApp must be used within AppProvider');
  }
  return context;
}


# === Redux Toolkit (moderne) ===

// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1; // Immer permet mutation
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export const store = configureStore({
  reducer: {
    counter: counterSlice.reducer
  }
});

// App.js
import { Provider } from 'react-redux';

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

// Component
import { useSelector, useDispatch } from 'react-redux';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
    </div>
  );
}


# === Zustand (simple et performant) ===

import create from 'zustand';

// Store
const useStore = create((set) => ({
  count: 0,
  user: null,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  setUser: (user) => set({ user }),
  reset: () => set({ count: 0, user: null })
}));

// Component
function Counter() {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);
  const decrement = useStore((state) => state.decrement);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

// Avec middleware (persist)
import { persist } from 'zustand/middleware';

const useStore = create(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    }),
    {
      name: 'counter-storage'
    }
  )
);


# === Jotai (atomic) ===

import { atom, useAtom } from 'jotai';

// Atoms
const countAtom = atom(0);
const userAtom = atom(null);

// Derived atoms
const doubleCountAtom = atom((get) => get(countAtom) * 2);

// Component
function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const [doubleCount] = useAtom(doubleCountAtom);
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {doubleCount}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}


# === Recoil ===

import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';

// Atom
const countState = atom({
  key: 'countState',
  default: 0
});

// Selector (derived state)
const doubleCountState = selector({
  key: 'doubleCountState',
  get: ({ get }) => {
    const count = get(countState);
    return count * 2;
  }
});

// Component
function Counter() {
  const [count, setCount] = useRecoilState(countState);
  const doubleCount = useRecoilValue(doubleCountState);
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {doubleCount}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}


[OK] DATA FETCHING


# === Fetch basique avec useEffect ===

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let cancelled = false;
    
    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        
        if (!response.ok) {
          throw new Error('Failed to fetch');
        }
        
        const data = await response.json();
        
        if (!cancelled) {
          setUser(data);
          setError(null);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }
    
    fetchUser();
    
    return () => {
      cancelled = true;
    };
  }, [userId]);
  
  if (loading) return <Spinner />;
  if (error) return <Error message={error} />;
  return <div>{user.name}</div>;
}


# === React Query / TanStack Query ===

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Query
function UserProfile({ userId }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json())
  });
  
  if (isLoading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  return <div>{data.name}</div>;
}

// Mutation
function CreateUser() {
  const queryClient = useQueryClient();
  
  const mutation = useMutation({
    mutationFn: (newUser) => {
      return fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(newUser)
      });
    },
    onSuccess: () => {
      // Invalider et refetch
      queryClient.invalidateQueries({ queryKey: ['users'] });
    }
  });
  
  const handleSubmit = (e) => {
    e.preventDefault();
    mutation.mutate({ name: 'John' });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <button type="submit" disabled={mutation.isPending}>
        {mutation.isPending ? 'Creating...' : 'Create User'}
      </button>
      {mutation.isError && <div>Error: {mutation.error.message}</div>}
    </form>
  );
}

// Pagination
function UserList() {
  const [page, setPage] = useState(1);
  
  const { data, isLoading } = useQuery({
    queryKey: ['users', page],
    queryFn: () => fetch(`/api/users?page=${page}`).then(res => res.json()),
    keepPreviousData: true
  });
  
  return (
    <div>
      {data?.users.map(user => <UserCard key={user.id} user={user} />)}
      
      <button onClick={() => setPage(page - 1)} disabled={page === 1}>
        Previous
      </button>
      <button onClick={() => setPage(page + 1)}>
        Next
      </button>
    </div>
  );
}

// Infinite query
function InfiniteUserList() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage
  } = useInfiniteQuery({
    queryKey: ['users'],
    queryFn: ({ pageParam = 1 }) =>
      fetch(`/api/users?page=${pageParam}`).then(res => res.json()),
    getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined
  });
  
  return (
    <div>
      {data?.pages.map((page, i) => (
        <div key={i}>
          {page.users.map(user => <UserCard key={user.id} user={user} />)}
        </div>
      ))}
      
      {hasNextPage && (
        <button onClick={fetchNextPage} disabled={isFetchingNextPage}>
          {isFetchingNextPage ? 'Loading...' : 'Load More'}
        </button>
      )}
    </div>
  );
}


# === SWR ===

import useSWR from 'swr';

const fetcher = (url) => fetch(url).then(res => res.json());

function UserProfile({ userId }) {
  const { data, error, isLoading, mutate } = useSWR(
    `/api/users/${userId}`,
    fetcher
  );
  
  if (isLoading) return <Spinner />;
  if (error) return <Error />;
  
  return (
    <div>
      <p>{data.name}</p>
      <button onClick={() => mutate()}>Refresh</button>
    </div>
  );
}

// Avec revalidation automatique
function LiveData() {
  const { data } = useSWR('/api/stats', fetcher, {
    refreshInterval: 3000, // Refetch toutes les 3s
    revalidateOnFocus: true,
    revalidateOnReconnect: true
  });
  
  return <div>Live stats: {data?.count}</div>;
}


[OK] TESTING


# === React Testing Library ===

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';

// Test simple
test('renders button with text', () => {
  render(<Button>Click me</Button>);
  
  const button = screen.getByRole('button', { name: /click me/i });
  expect(button).toBeInTheDocument();
});

// Test événements
test('calls onClick when clicked', () => {
  const handleClick = jest.fn();
  render(<Button onClick={handleClick}>Click</Button>);
  
  const button = screen.getByRole('button');
  fireEvent.click(button);
  
  expect(handleClick).toHaveBeenCalledTimes(1);
});

// Avec userEvent (plus réaliste)
test('types into input', async () => {
  const user = userEvent.setup();
  render(<LoginForm />);
  
  const input = screen.getByLabelText(/email/i);
  await user.type(input, 'test@example.com');
  
  expect(input).toHaveValue('test@example.com');
});

// Test async
test('loads and displays user', async () => {
  render(<UserProfile userId={1} />);
  
  expect(screen.getByText(/loading/i)).toBeInTheDocument();
  
  await waitFor(() => {
    expect(screen.getByText(/john doe/i)).toBeInTheDocument();
  });
});

// Mock fetch
test('fetches user data', async () => {
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve({ name: 'John Doe' })
    })
  );
  
  render(<UserProfile userId={1} />);
  
  await waitFor(() => {
    expect(screen.getByText('John Doe')).toBeInTheDocument();
  });
  
  expect(fetch).toHaveBeenCalledWith('/api/users/1');
});

// Test formulaire
test('submits form with correct data', async () => {
  const user = userEvent.setup();
  const handleSubmit = jest.fn();
  
  render(<LoginForm onSubmit={handleSubmit} />);
  
  await user.type(screen.getByLabelText(/email/i), 'test@example.com');
  await user.type(screen.getByLabelText(/password/i), 'password123');
  await user.click(screen.getByRole('button', { name: /submit/i }));
  
  expect(handleSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'password123'
  });
});

// Test avec Context
test('uses theme from context', () => {
  render(
    <ThemeProvider>
      <ThemedButton />
    </ThemeProvider>
  );
  
  const button = screen.getByRole('button');
  expect(button).toHaveClass('light-theme');
});

// Test hooks personnalisés
import { renderHook, act } from '@testing-library/react';

test('useCounter increments', () => {
  const { result } = renderHook(() => useCounter());
  
  expect(result.current.count).toBe(0);
  
  act(() => {
    result.current.increment();
  });
  
  expect(result.current.count).toBe(1);
});


# === Snapshot testing ===

import renderer from 'react-test-renderer';

test('matches snapshot', () => {
  const tree = renderer.create(<Button>Click me</Button>).toJSON();
  expect(tree).toMatchSnapshot();
});


[OK] TYPESCRIPT


# === Props typing ===

// Interface pour props
interface ButtonProps {
  children: React.ReactNode;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

const Button: React.FC<ButtonProps> = ({
  children,
  onClick,
  variant = 'primary',
  disabled = false
}) => {
  return (
    <button onClick={onClick} disabled={disabled} className={variant}>
      {children}
    </button>
  );
};

// Type pour props
type InputProps = {
  value: string;
  onChange: (value: string) => void;
  placeholder?: string;
};

const Input: React.FC<InputProps> = ({ value, onChange, placeholder }) => {
  return (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
      placeholder={placeholder}
    />
  );
};

// Props avec générics
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  keyExtractor: (item: T) => string;
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item) => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// Usage
<List
  items={users}
  renderItem={(user) => <UserCard user={user} />}
  keyExtractor={(user) => user.id}
/>


# === useState avec TypeScript ===

// Type inféré automatiquement
const [count, setCount] = useState(0); // number
const [name, setName] = useState(''); // string

// Type explicite
const [user, setUser] = useState<User | null>(null);

interface User {
  id: string;
  name: string;
  email: string;
}

const [users, setUsers] = useState<User[]>([]);

// Type avec initialisation paresseuse
const [data, setData] = useState<Data>(() => computeInitialData());


# === useRef avec TypeScript ===

// Ref DOM
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
  inputRef.current?.focus();
}, []);

// Ref valeur mutable
const countRef = useRef<number>(0);
countRef.current = 5;

// Ref avec valeur initiale non-null
const intervalRef = useRef<NodeJS.Timeout | null>(null);


# === useContext avec TypeScript ===

interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

function useTheme() {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}


# === Events avec TypeScript ===

// Event handlers
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  console.log(e.currentTarget);
};

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value);
};

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
  if (e.key === 'Enter') {
    console.log('Enter pressed');
  }
};

// Custom event handler type
type ButtonClickHandler = (id: string) => void;

interface ButtonProps {
  onClick: ButtonClickHandler;
}


# === Children types ===

// ReactNode (le plus courant)
interface Props {
  children: React.ReactNode;
}

// ReactElement (composant React spécifique)
interface Props {
  children: React.ReactElement;
}

// Function as children
interface Props {
  children: (data: Data) => React.ReactNode;
}

// Multiple children types
interface Props {
  children: React.ReactNode | ((data: Data) => React.ReactNode);
}


# === forwardRef avec TypeScript ===

interface InputProps {
  label: string;
}

const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
  return (
    <div>
      <label>{props.label}</label>
      <input ref={ref} />
    </div>
  );
});


# === Custom hooks avec TypeScript ===

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then((data: T) => {
        setData(data);
        setLoading(false);
      })
      .catch((err: Error) => {
        setError(err);
        setLoading(false);
      });
  }, [url]);
  
  return { data, loading, error };
}

// Usage
interface User {
  id: string;
  name: string;
}

const { data, loading } = useFetch<User[]>('/api/users');


# === Component types ===

// Functional component
const Button: React.FC<ButtonProps> = (props) => {
  return <button>{props.children}</button>;
};

// Alternative (recommandé)
function Button(props: ButtonProps): React.ReactElement {
  return <button>{props.children}</button>;
}

// With generics
function Select<T>({ options, value, onChange }: SelectProps<T>) {
  // ...
}


[OK] BONNES PRATIQUES


# === Structure de dossiers ===

src/
├── components/
│   ├── common/              # Composants réutilisables
│   │   ├── Button/
│   │   │   ├── Button.tsx
│   │   │   ├── Button.test.tsx
│   │   │   ├── Button.module.css
│   │   │   └── index.ts
│   │   └── Input/
│   ├── layout/              # Layout components
│   │   ├── Header.tsx
│   │   ├── Footer.tsx
│   │   └── Sidebar.tsx
│   └── features/            # Feature-specific
│       ├── auth/
│       │   ├── LoginForm.tsx
│       │   └── RegisterForm.tsx
│       └── users/
│           ├── UserList.tsx
│           └── UserProfile.tsx
├── hooks/                   # Custom hooks
│   ├── useAuth.ts
│   ├── useFetch.ts
│   └── useLocalStorage.ts
├── contexts/                # Context providers
│   ├── AuthContext.tsx
│   └── ThemeContext.tsx
├── utils/                   # Utilities
│   ├── api.ts
│   ├── helpers.ts
│   └── constants.ts
├── types/                   # TypeScript types
│   ├── user.ts
│   └── api.ts
├── pages/                   # Pages/Routes
│   ├── Home.tsx
│   ├── About.tsx
│   └── Users.tsx
├── services/                # API services
│   ├── authService.ts
│   └── userService.ts
├── store/                   # State management
│   ├── slices/
│   └── store.ts
└── App.tsx


# === Naming conventions ===

// Composants: PascalCase
function UserProfile() {}
const Button = () => {};

// Hooks: camelCase avec préfixe "use"
function useAuth() {}
const useLocalStorage = () => {};

// Utilitaires: camelCase
function formatDate() {}
const calculateTotal = () => {};

// Constants: UPPER_SNAKE_CASE
const API_BASE_URL = 'https://api.example.com';
const MAX_ITEMS = 100;

// Types/Interfaces: PascalCase
interface User {}
type UserRole = 'admin' | 'user';


# === Règles de composition ===

// [OK] Bon: Petits composants avec responsabilité unique
function UserCard({ user }) {
  return (
    <div className="card">
      <UserAvatar src={user.avatar} />
      <UserInfo name={user.name} email={user.email} />
      <UserActions userId={user.id} />
    </div>
  );
}

// [X] Mauvais: Composant monolithique
function UserCard({ user }) {
  return (
    <div className="card">
      {/* 200 lignes de JSX... */}
    </div>
  );
}

// [OK] Bon: Extraction de logique dans hooks
function UserProfile({ userId }) {
  const { user, loading, error } = useUser(userId);
  const { follow, unfollow, isFollowing } = useFollow(userId);
  
  if (loading) return <Spinner />;
  return <div>{/* ... */}</div>;
}

// [X] Mauvais: Toute la logique dans le composant
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  // 50 lignes de logique...
}


# === Performance tips ===

// [OK] Utiliser key unique et stable
{items.map(item => (
  <Item key={item.id} data={item} />
))}

// [X] Éviter index comme key si liste peut changer
{items.map((item, index) => (
  <Item key={index} data={item} />
))}

// [OK] Mémoïser calculs coûteux
const sortedItems = useMemo(() => 
  items.sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

// [OK] Mémoïser callbacks pour composants mémoïsés
const MemoizedChild = memo(Child);

function Parent() {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return <MemoizedChild onClick={handleClick} />;
}

// [OK] Code splitting pour routes
const Dashboard = lazy(() => import('./pages/Dashboard'));

// [OK] Virtualisation pour longues listes
import { FixedSizeList } from 'react-window';


# === État et immutabilité ===

// [OK] Bon: Nouvelle référence avec spread
setUser({ ...user, name: 'John' });

// [X] Mauvais: Mutation directe
user.name = 'John';
setUser(user);

// [OK] Bon: Nouveau tableau
setItems([...items, newItem]);
setItems(items.filter(item => item.id !== id));

// [X] Mauvais: Mutation de tableau
items.push(newItem);
setItems(items);

// [OK] Bon: Mise à jour avec fonction
setCount(prev => prev + 1);

// [X] Risqué: Mise à jour directe (problèmes avec batching)
setCount(count + 1);


# === Gestion d'erreurs ===

// [OK] Error boundaries pour erreurs de rendu
<ErrorBoundary fallback={<ErrorPage />}>
  <App />
</ErrorBoundary>

// [OK] Try-catch pour async
useEffect(() => {
  async function fetchData() {
    try {
      const data = await api.getUsers();
      setUsers(data);
    } catch (error) {
      setError(error.message);
    }
  }
  fetchData();
}, []);

// [OK] Cleanup pour éviter memory leaks
useEffect(() => {
  const subscription = api.subscribe();
  
  return () => {
    subscription.unsubscribe();
  };
}, []);


# === Accessibilité ===

// [OK] Labels pour inputs
<label htmlFor="email">Email</label>
<input id="email" type="email" />

// [OK] Alt text pour images
<img src={url} alt="User profile picture" />

// [OK] ARIA attributes
<button aria-label="Close modal" onClick={onClose}>
  <CloseIcon />
</button>

// [OK] Roles sémantiques
<nav role="navigation">
  <ul role="list">
    <li role="listitem">
      <a href="/">Home</a>
    </li>
  </ul>
</nav>

// [OK] Keyboard navigation
<button
  onClick={handleClick}
  onKeyDown={(e) => e.key === 'Enter' && handleClick()}
  tabIndex={0}
>
  Click me
</button>


# === Sécurité ===

// [OK] Échapper contenu utilisateur (React le fait automatiquement)
<div>{user.name}</div>

// [X] Attention avec dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userContent }} />

// [OK] Sanitiser si nécessaire
import DOMPurify from 'dompurify';

<div dangerouslySetInnerHTML={{
  __html: DOMPurify.sanitize(userContent)
}} />

// [OK] Valider inputs côté client ET serveur
const validateEmail = (email) => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};


# === Environment variables ===

// Avec Create React App
REACT_APP_API_URL=https://api.example.com
REACT_APP_API_KEY=your_key

// Dans le code
const apiUrl = process.env.REACT_APP_API_URL;

// Avec Vite
VITE_API_URL=https://api.example.com

const apiUrl = import.meta.env.VITE_API_URL;


[OK] STYLING


# === CSS Modules ===

// Button.module.css
.button {
  padding: 10px 20px;
  background: blue;
}

.button:hover {
  background: darkblue;
}

// Button.tsx
import styles from './Button.module.css';

function Button({ children }) {
  return <button className={styles.button}>{children}</button>;
}

// Composition de classes
<button className={`${styles.button} ${styles.primary}`}>
  Click me
</button>

// Avec classnames
import classNames from 'classnames';

<button className={classNames(styles.button, {
  [styles.primary]: isPrimary,
  [styles.disabled]: isDisabled
})}>
  Click me
</button>


# === Styled Components ===

import styled from 'styled-components';

// Styled component basique
const Button = styled.button`
  padding: 10px 20px;
  background: ${props => props.primary ? 'blue' : 'gray'};
  color: white;
  border: none;
  border-radius: 4px;
  
  &:hover {
    opacity: 0.8;
  }
`;

// Usage
<Button primary>Click me</Button>

// Extension de styles
const PrimaryButton = styled(Button)`
  background: blue;
`;

// Styled component avec props
interface ButtonProps {
  size?: 'small' | 'medium' | 'large';
}

const Button = styled.button<ButtonProps>`
  padding: ${props => {
    switch (props.size) {
      case 'small': return '5px 10px';
      case 'large': return '15px 30px';
      default: return '10px 20px';
    }
  }};
`;

// Theme provider
import { ThemeProvider } from 'styled-components';

const theme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d'
  }
};

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button />
    </ThemeProvider>
  );
}

const Button = styled.button`
  background: ${props => props.theme.colors.primary};
`;

// Global styles
import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  
  body {
    font-family: 'Arial', sans-serif;
  }
`;

function App() {
  return (
    <>
      <GlobalStyle />
      <MainContent />
    </>
  );
}


# === Tailwind CSS ===

// Configuration classique
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
  Click me
</button>

// Responsive
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
  {/* ... */}
</div>

// États conditionnels
<button className={`
  px-4 py-2 rounded
  ${isPrimary ? 'bg-blue-500' : 'bg-gray-500'}
  ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:opacity-80'}
`}>
  Click me
</button>

// Avec clsx
import clsx from 'clsx';

<button className={clsx(
  'px-4 py-2 rounded',
  isPrimary ? 'bg-blue-500' : 'bg-gray-500',
  isDisabled && 'opacity-50 cursor-not-allowed'
)}>
  Click me
</button>


# === Emotion ===

import { css } from '@emotion/react';
import styled from '@emotion/styled';

// CSS prop
<div css={css`
  padding: 20px;
  background: blue;
`}>
  Content
</div>

// Styled components
const Button = styled.button`
  padding: 10px 20px;
  background: ${props => props.primary ? 'blue' : 'gray'};
`;


# === Inline styles (éviter généralement) ===

// Style objet
const style = {
  padding: '10px 20px',
  backgroundColor: 'blue',
  color: 'white'
};

<button style={style}>Click me</button>

// Inline direct
<button style={{
  padding: '10px 20px',
  backgroundColor: 'blue',
  color: 'white'
}}>
  Click me
</button>

// Dynamique
<button style={{
  backgroundColor: isPrimary ? 'blue' : 'gray'
}}>
  Click me
</button>


[OK] BUILD & DEPLOYMENT


# === Create React App ===

# Créer application
npx create-react-app my-app
npx create-react-app my-app --template typescript

# Démarrer dev server
npm start

# Build production
npm run build

# Tester build localement
npx serve -s build

# Éjecter (irréversible!)
npm run eject


# === Vite (moderne et rapide) ===

# Créer application
npm create vite@latest my-app -- --template react
npm create vite@latest my-app -- --template react-ts

# Démarrer dev server
npm run dev

# Build production
npm run build

# Preview build
npm run preview

# Configuration vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    port: 3000,
    open: true
  },
  build: {
    outDir: 'dist',
    sourcemap: true
  }
});


# === Next.js (SSR/SSG) ===

# Créer application
npx create-next-app@latest my-app
npx create-next-app@latest my-app --typescript

# Démarrer dev server
npm run dev

# Build production
npm run build

# Démarrer production
npm start

# Export static
npm run build && npm run export


# === Variables d'environnement ===

# .env.local
REACT_APP_API_URL=http://localhost:3000
REACT_APP_API_KEY=secret_key

# .env.production
REACT_APP_API_URL=https://api.production.com
REACT_APP_API_KEY=prod_key

# Utilisation
const apiUrl = process.env.REACT_APP_API_URL;


# === Optimization ===

// Code splitting
const Dashboard = lazy(() => import('./Dashboard'));

// Tree shaking (imports nommés)
import { useState, useEffect } from 'react'; // [OK]
import React from 'react'; // [X] (importe tout)

// Dynamic imports
button.addEventListener('click', async () => {
  const module = await import('./heavyModule.js');
  module.doSomething();
});

// Webpack bundle analyzer
npm install --save-dev webpack-bundle-analyzer

// package.json
"analyze": "source-map-explorer 'build/static/js/*.js'"


# === Déploiement ===

# Netlify
# 1. Build command: npm run build
# 2. Publish directory: build
# 3. Drag & drop ou Git integration

# Vercel
npm install -g vercel
vercel

# GitHub Pages
npm install --save-dev gh-pages

# package.json
"homepage": "https://username.github.io/repo-name",
"scripts": {
  "predeploy": "npm run build",
  "deploy": "gh-pages -d build"
}

npm run deploy

# Docker
# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

# Build et run
docker build -t my-react-app .
docker run -p 80:80 my-react-app


[OK] DEBUGGING & DEVTOOLS


# === React DevTools ===

# Installation
# Extension Chrome/Firefox: React Developer Tools

# Utilisation
# - Inspecter composants
# - Voir props et state
# - Profiler performance
# - Identifier re-renders


# === Console debugging ===

// Log simple
console.log('Value:', value);

// Table pour objets/tableaux
console.table(users);

// Groupes
console.group('User Info');
console.log('Name:', user.name);
console.log('Email:', user.email);
console.groupEnd();

// Temps d'exécution
console.time('fetchUsers');
await fetchUsers();
console.timeEnd('fetchUsers');

// Trace de la pile
console.trace('Debug point');


# === React strict mode ===

import { StrictMode } from 'react';

// Aide à détecter problèmes en développement
<StrictMode>
  <App />
</StrictMode>

// Comportements:
// - Détecte effets secondaires dans render
// - Warn sur API dépréciées
// - Double invocation de effects (dev seulement)


# === Error boundaries logging ===

class ErrorBoundary extends Component {
  componentDidCatch(error, errorInfo) {
    // Log vers service externe
    logErrorToService(error, errorInfo);
    
    // Sentry
    Sentry.captureException(error, {
      contexts: {
        react: { componentStack: errorInfo.componentStack }
      }
    });
  }
}


# === Performance monitoring ===

import { Profiler } from 'react';

<Profiler id="App" onRender={(id, phase, actualDuration) => {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}}>
  <App />
</Profiler>

// Why Did You Render
import whyDidYouRender from '@welldone-software/why-did-you-render';

if (process.env.NODE_ENV === 'development') {
  whyDidYouRender(React, {
    trackAllPureComponents: true
  });
}


[OK] RESSOURCES & ÉCOSYSTÈME


# === Documentation officielle ===
https://react.dev/
https://react.dev/learn
https://react.dev/reference/react

# === Bibliothèques populaires ===

# UI Components
- Material-UI (MUI): https://mui.com/
- Ant Design: https://ant.design/
- Chakra UI: https://chakra-ui.com/
- Mantine: https://mantine.dev/
- shadcn/ui: https://ui.shadcn.com/

# Formulaires
- React Hook Form: https://react-hook-form.com/
- Formik: https://formik.org/
- Final Form: https://final-form.org/

# State Management
- Redux Toolkit: https://redux-toolkit.js.org/
- Zustand: https://github.com/pmndrs/zustand
- Jotai: https://jotai.org/
- Recoil: https://recoiljs.org/
- MobX: https://mobx.js.org/

# Data Fetching
- TanStack Query: https://tanstack.com/query
- SWR: https://swr.vercel.app/
- Apollo Client: https://www.apollographql.com/docs/react/

# Routing
- React Router: https://reactrouter.com/
- TanStack Router: https://tanstack.com/router

# Animation
- Framer Motion: https://www.framer.com/motion/
- React Spring: https://react-spring.dev/
- GSAP: https://greensock.com/react/

# Testing
- React Testing Library: https://testing-library.com/react
- Jest: https://jestjs.io/
- Vitest: https://vitest.dev/
- Cypress: https://www.cypress.io/
- Playwright: https://playwright.dev/

# Build Tools
- Vite: https://vitejs.dev/
- Create React App: https://create-react-app.dev/
- Next.js: https://nextjs.org/
- Remix: https://remix.run/

# Styling
- Tailwind CSS: https://tailwindcss.com/
- Styled Components: https://styled-components.com/
- Emotion: https://emotion.sh/
- CSS Modules: https://github.com/css-modules/css-modules

# Utilities
- date-fns: https://date-fns.org/
- Lodash: https://lodash.com/
- Ramda: https://ramdajs.com/
- immer: https://immerjs.github.io/immer/

# Icons
- React Icons: https://react-icons.github.io/react-icons/
- Lucide React: https://lucide.dev/
- Heroicons: https://heroicons.com/

# Drag & Drop
- dnd kit: https://dndkit.com/
- react-beautiful-dnd: https://github.com/atlassian/react-beautiful-dnd

# Charts
- Recharts: https://recharts.org/
- Victory: https://formidable.com/open-source/victory/
- Chart.js: https://www.chartjs.org/

# Tables
- TanStack Table: https://tanstack.com/table
- react-table: https://react-table.tanstack.com/

# Virtualisation
- react-window: https://github.com/bvaughn/react-window
- react-virtualized: https://github.com/bvaughn/react-virtualized


[OK] PATTERNS ANTI-PATTERNS


# === [OK] BONNES PRATIQUES ===

// Extraire logique réutilisable
function useUser(userId) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  return user;
}

// Composition sur héritage
function UserCard({ user }) {
  return (
    <Card>
      <Avatar src={user.avatar} />
      <UserInfo user={user} />
    </Card>
  );
}

// Props drilling: utiliser Context
<ThemeProvider>
  <App />
</ThemeProvider>

// Séparer logique et présentation
function UserListContainer() {
  const { users, loading } = useUsers();
  return <UserListPresentation users={users} loading={loading} />;
}


# === [X] ANTI-PATTERNS ===

// [X] Modifier props
function Component({ user }) {
  user.name = 'New Name'; // Jamais!
}

// [X] Appeler hooks conditionnellement
if (condition) {
  const [state, setState] = useState(0); // Erreur!
}

// [X] Mutation directe du state
const [items, setItems] = useState([]);
items.push(newItem); // Non!
setItems(items);

// [X] Oublier dependencies dans useEffect
useEffect(() => {
  fetchData(userId); // userId devrait être dans les dépendances!
}, []);

// [X] Mettre trop de logique dans JSX
return (
  <div>
    {items.filter(item => item.active)
      .sort((a, b) => a.name.localeCompare(b.name))
      .map(item => <Item key={item.id} item={item} />)}
  </div>
);

// [X] Prop drilling excessif (5+ niveaux)
<A>
  <B data={data}>
    <C data={data}>
      <D data={data}>
        <E data={data} />
      </D>
    </C>
  </B>
</A>


[OK] AIDE-MÉMOIRE RAPIDE


# Lifecycle équivalents (Class -> Hooks)

componentDidMount          -> useEffect(() => {}, [])
componentDidUpdate         -> useEffect(() => {})
componentWillUnmount       -> useEffect(() => { return () => {} }, [])
shouldComponentUpdate      -> React.memo()
getDerivedStateFromProps   -> useState + useEffect
forceUpdate                -> useState avec setter vide


# Hooks essentiels

useState        -> État local
useEffect       -> Effets secondaires
useContext      -> Consommer contexte
useReducer      -> État complexe
useCallback     -> Mémoïser fonction
useMemo         -> Mémoïser valeur
useRef          -> Référence mutable
useLayoutEffect -> Effet synchrone DOM


# Commandes courantes

npx create-react-app app    -> Créer app (CRA)
npm create vite@latest      -> Créer app (Vite)
npm start / npm run dev     -> Démarrer dev
npm run build               -> Build production
npm test                    -> Lancer tests


# JSX

{expression}                -> Évaluer JS
{condition && <Component/>} -> Rendu conditionnel
{condition ? <A/> : <B/>}   -> Ternaire
{items.map(i => <Li key={i.id}/>)} -> Liste
<Component {...props}/>     -> Spread props
<>...</>                    -> Fragment


# Performance

React.memo()                -> Mémoïser composant
useMemo()                   -> Mémoïser valeur
useCallback()               -> Mémoïser fonction
lazy() + Suspense           -> Code splitting
key={stable-id}             -> Listes optimisées