Cours Next.js · Module 1: Configuration de Next.js

Système de composants - composants client et serveur

14 min de lecture
Dans cette leçon10

À Quantum Metropolis, les architectes des systèmes quantiques doivent toujours prendre des décisions fondamentales : quels processus exécuter sur les processeurs quantiques centraux (pour les tâches nécessitant la plus grande puissance de calcul) et lesquels exécuter sur les appareils des citoyens (pour une interactivité instantanée). Cette dichotomie reflète parfaitement le changement révolutionnaire que l'App Router a introduit dans Next.js 13 et que Next.js 16 prolonge - la division entre Server Components et Client Components.

La révolution des React Server Components

Les React Server Components (RSC) représentent le plus grand changement de paradigme dans React depuis l'introduction des Hooks. Avant leur apparition, tous les composants React étaient rendus côté serveur ou côté client comme un tout cohérent. Désormais, nous pouvons créer des applications où certains composants sont rendus exclusivement sur le serveur, d'autres sur le client, et où les données et les structures d'interface circulent harmonieusement entre eux.

C'est comme la différence entre les systèmes de transport traditionnels de Quantum Metropolis, où les véhicules se déplacent uniquement le long de routes précises, et le nouveau système de transport quantique, où la matière peut être téléportée directement à destination.

Server Components - les bases

Dans l'App Router de Next.js 16, tous les composants sont des Server Components par défaut, à moins que nous ne les marquions explicitement comme Client Components.

Les Server Components sont rendus sur le serveur et n'envoient aucun JavaScript au navigateur. C'est comme les processeurs de calcul quantique de Quantum Metropolis, qui effectuent des calculs complexes et ne transmettent que les résultats finaux aux appareils des citoyens.

Avantages des Server Components :

  1. Accès aux ressources serveur - accès direct aux bases de données, au système de fichiers et à d'autres ressources serveur
  2. Sécurité - les données et la logique sensibles restent sur le serveur
  3. Découpage automatique du code - seul le code nécessaire est envoyé au client
  4. Taille de bundle JavaScript plus petite - pas besoin d'envoyer le code de rendu
  5. Meilleur SEO - rendu statique plus rapide
  6. Meilleures performances au premier chargement - moins de JavaScript à télécharger et à exécuter

Exemple de Server Component :

1// app/dashboard/stats/page.tsx - Server Component (by default)
2export default async function StatsPage() {
3  // This code runs ONLY on the server
4  const stats = await fetchQuantumSystemStats();
5
6  return (
7    <section className="quantum-stats">
8      <h1>Quantum Systems Statistics</h1>
9      <div className="stats-grid">
10        {stats.map(stat => (
11          <StatCard key={stat.id} name={stat.name} value={stat.value} />
12        ))}
13      </div>
14    </section>
15  );
16}

Remarque plusieurs aspects importants :

  1. L'utilisation directe de async/await dans le composant - c'est possible uniquement dans les Server Components
  2. Pas d'utilisation des hooks React (useState, useEffect, etc.) - ils ne sont pas disponibles dans les Server Components
  3. Pas de gestion d'événements - les Server Components ne peuvent pas gérer les événements utilisateur

Client Components - les bases

Les Client Components apportent l'interactivité : à la première visite, Next.js les pré-rend en HTML sur le serveur, puis ils sont hydratés dans le navigateur et continuent de fonctionner côté client. Pour marquer un composant comme Client Component, nous ajoutons la directive "use client" en haut du fichier.

Ce processus s'appelle l'hydratation : dans le navigateur, React attache les gestionnaires d'événements au HTML rendu plus tôt sur le serveur. Ce n'est qu'après l'hydratation que les boutons réagissent aux clics, et les effets de useEffect s'exécutent après elle.

C'est comme les appareils quantiques personnels des citoyens de Quantum Metropolis - plus légers, mais réagissant instantanément aux actions de l'utilisateur.

Avantages des Client Components :

  1. Interactivité - gestion des événements utilisateur (clics, saisie de texte, etc.)
  2. Hooks React - possibilité d'utiliser useState, useEffect, useContext et d'autres hooks
  3. Accès aux API du navigateur - window, document, localStorage, etc.
  4. Effets côté client - animations, interactions en temps réel

Exemple de Client Component :

1// app/dashboard/interactive-controls.tsx
2"use client"; // This directive marks the client component
3
4import { useState, useEffect } from 'react';
5
6export default function InteractiveControls() {
7  // This code runs in the user's browser
8  const [activeSystem, setActiveSystem] = useState('energy');
9  const [status, setStatus] = useState('idle');
10
11  useEffect(() => {
12    // Effects run only on the client
13    const timer = setTimeout(() => {
14      setStatus('ready');
15    }, 1000);
16
17    return () => clearTimeout(timer);
18  }, []);
19
20  return (
21    <div className="control-panel">
22      <h2>System: {activeSystem}</h2>
23      <p>Status: {status}</p>
24      <div className="button-group">
25        <button onClick={() => setActiveSystem('transport')}>
26          Transport
27        </button>
28        <button onClick={() => setActiveSystem('energy')}>
29          Energy
30        </button>
31        <button onClick={() => setActiveSystem('security')}>
32          Security
33        </button>
34      </div>
35    </div>
36  );
37}

Aspects clés :

  1. La directive "use client" au début du fichier
  2. L'utilisation des hooks React (useState, useEffect)
  3. La gestion des événements (onClick)

Combiner Server et Client Components

La fonctionnalité la plus puissante du nouveau modèle est la possibilité de combiner Server et Client Components dans une seule application. Les Server Components peuvent rendre des Client Components, mais pas l'inverse (avec quelques exceptions utilisant des patterns). C'est comme le système de transport hybride de Quantum Metropolis, où les routes principales sont assurées par la téléportation quantique, et le dernier kilomètre par des véhicules personnels.

Exemple de combinaison de composants :

1// app/dashboard/page.tsx - Server Component
2export default async function DashboardPage() {
3  // This code runs ONLY on the server
4  const dashboardData = await fetchDashboardData();
5
6  return (
7    <main className="quantum-dashboard">
8      <h1>Quantum Metropolis Control Panel</h1>
9
10      {/* Server Component */}
11      <SystemStatusPanel systemsData={dashboardData.systems} />
12
13      {/* Client Component (imported and used in Server Component) */}
14      <InteractiveControls />
15    </main>
16  );
17}
1// app/dashboard/system-status-panel.tsx - Server Component (by default)
2export default function SystemStatusPanel({ systemsData }) {
3  return (
4    <div className="system-status-panel">
5      <h2>System Status</h2>
6      <div className="status-grid">
7        {systemsData.map(system => (
8          <SystemStatusCard key={system.id} system={system} />
9        ))}
10      </div>
11    </div>
12  );
13}
1// app/dashboard/system-status-card.tsx - Server Component (by default)
2import { StatusIndicator } from './status-indicator';
3
4export default function SystemStatusCard({ system }) {
5  return (
6    <div className="status-card">
7      <h3>{system.name}</h3>
8      <p>Performance: {system.performance}%</p>
9
10      {/* Client Component used in Server Component */}
11      <StatusIndicator status={system.status} />
12    </div>
13  );
14}
1// app/dashboard/status-indicator.tsx - Client Component
2"use client";
3
4import { useState, useEffect } from 'react';
5
6export function StatusIndicator({ status }) {
7  const [blinking, setBlinking] = useState(false);
8
9  useEffect(() => {
10    const interval = setInterval(() => {
11      setBlinking(prev => !prev);
12    }, 500);
13
14    return () => clearInterval(interval);
15  }, []);
16
17  return (
18    <div className={`status-indicator ${status} ${blinking ? 'blink' : ''}`}>
19      {status === 'active' ? 'Active' : 'Inactive'}
20    </div>
21  );
22}

Stratégies d'optimisation lors de la conception des composants

Décider correctement quels composants doivent être des Server Components et lesquels doivent être des Client Components est crucial pour les performances de l'application. Voici quelques stratégies pour t'aider à prendre ces décisions :

1. Le Server Component comme choix par défaut

Commence toujours par des Server Components, sauf si tu as besoin de fonctionnalités disponibles uniquement dans les Client Components. C'est similaire à l'approche de Quantum Metropolis, où les unités de calcul centrales sont utilisées par défaut, sauf si une interactivité instantanée est nécessaire.

2. Interactivité granulaire

Au lieu de marquer un grand composant entier comme « use client », extrait uniquement les parties interactives dans des Client Components séparés, en laissant le reste en Server Components.

Avant optimisation :

1// LESS OPTIMAL - entire component is a Client Component
2"use client";
3import { useState } from 'react';
4
5export default function ControlPanel() {
6  const [activeTab, setActiveTab] = useState('overview');
7
8  return (
9    <div className="control-panel">
10      <h2>Control Panel</h2>
11      <p>This panel allows you to manage quantum systems.</p>
12
13      <div className="tabs">
14        <button onClick={() => setActiveTab('overview')}>Overview</button>
15        <button onClick={() => setActiveTab('settings')}>Settings</button>
16      </div>
17
18      <div className="tab-content">
19        {activeTab === 'overview' ? (
20          <OverviewContent /> // Lots of static content
21        ) : (
22          <SettingsContent /> // Lots of static content
23        )}
24      </div>
25    </div>
26  );
27}

Après optimisation :

1// BETTER - split into Client and Server Components
2
3// control-panel.tsx - Server Component
4import { TabController } from './tab-controller';
5import { OverviewContent } from './overview-content';
6import { SettingsContent } from './settings-content';
7
8export default function ControlPanel() {
9  return (
10    <div className="control-panel">
11      <h2>Control Panel</h2>
12      <p>This panel allows you to manage quantum systems.</p>
13
14      {/* Only TabController is a Client Component; the tab contents stay on the server */}
15      <TabController overview={<OverviewContent />} settings={<SettingsContent />} />
16    </div>
17  );
18}
19
20// tab-controller.tsx - Client Component
21"use client";
22import { useState, type ReactNode } from 'react';
23
24export function TabController({ overview, settings }: { overview: ReactNode; settings: ReactNode }) {
25  const [activeTab, setActiveTab] = useState('overview');
26
27  return (
28    <>
29      <div className="tabs">
30        <button onClick={() => setActiveTab('overview')}>Overview</button>
31        <button onClick={() => setActiveTab('settings')}>Settings</button>
32      </div>
33
34      <div className="tab-content">
35        {activeTab === 'overview' ? overview : settings}
36      </div>
37    </>
38  );
39}

Un composant importé dans un fichier avec "use client" devient lui aussi un Client Component. C'est pourquoi le Server Component ControlPanel importe OverviewContent et SettingsContent et les passe à TabController comme props, tout comme children.

3. Le pattern « Container et Présentation »

Utilise les Server Components comme « containers » pour la récupération de données et l'organisation, et les Client Components comme « composants de présentation » responsables de l'interactivité.

1// products-container.tsx - Server Component (container)
2export default async function ProductsContainer() {
3  const products = await fetchProducts();
4
5  return <ProductsList products={products} />;
6}
7
8// products-list.tsx - Client Component (presentation)
9"use client";
10import { useState } from 'react';
11
12export function ProductsList({ products }) {
13  const [sortBy, setSortBy] = useState('name');
14  const sortedProducts = [...products].sort((a, b) =>
15    sortBy === 'price' ? a.price - b.price : a.name.localeCompare(b.name)
16  );
17
18  return (
19    <div>
20      <div className="sorting">
21        <button onClick={() => setSortBy('name')}>Sort by name</button>
22        <button onClick={() => setSortBy('price')}>Sort by price</button>
23      </div>
24
25      <ul className="products">
26        {sortedProducts.map(product => (
27          <li key={product.id}>{product.name} - {product.price}</li>
28        ))}
29      </ul>
30    </div>
31  );
32}

Flux de données entre Server et Client Components

Passer des données entre le serveur et le client nécessite une approche particulière. Dans le modèle RSC, les props descendent dans l'arbre des composants du serveur vers le client ; dans l'autre sens, le client envoie des données en appelant des Server Actions (tu les découvriras dans un instant). C'est comme les canaux quantiques de Quantum Metropolis - les ordres descendent du hub central vers les appareils personnels, et les rapports remontent par un canal séparé.

Passer des données aux Client Components :

1// server-component.tsx - Server Component
2async function ServerComponent() {
3  const data = await fetchSensitiveData();
4
5  // Pass processed data to client
6  return <ClientComponent processedData={data.publicPart} />;
7}
8
9// client-component.tsx - Client Component
10"use client";
11
12export function ClientComponent({ processedData }) {
13  // Using data passed from the server
14  return <div>{processedData.map(item => <p key={item.id}>{item.name}</p>)}</div>;
15}

Gérer les actions utilisateur depuis les Client Components :

Quand un Client Component doit effectuer une action impliquant le serveur, nous utilisons les Server Actions ou les Route Handlers (fichiers route.ts ; dans le Pages Router, leur équivalent s'appelle API Routes).

Utiliser les Server Actions (stables depuis Next.js 14) :

1// actions.js - Server Actions
2"use server";
3
4export async function updateSystemStatus(systemId, newStatus) {
5  // This function runs on the server
6  await database.systems.update(systemId, { status: newStatus });
7  return { success: true };
8}
9
10// client-component.tsx - Client Component
11"use client";
12import { useState } from 'react';
13import { updateSystemStatus } from './actions';
14
15export function StatusToggle({ systemId, initialStatus }) {
16  const [status, setStatus] = useState(initialStatus);
17
18  async function handleToggle() {
19    const newStatus = status === 'active' ? 'inactive' : 'active';
20    const result = await updateSystemStatus(systemId, newStatus);
21
22    if (result.success) {
23      setStatus(newStatus);
24    }
25  }
26
27  return (
28    <button onClick={handleToggle}>
29      {status === 'active' ? 'Deactivate' : 'Activate'}
30    </button>
31  );
32}

Différences de rechargement et de navigation

Dans le modèle React traditionnel, le rechargement d'une page ou la navigation signifiait souvent redémarrer toute l'application. Dans le modèle des React Server Components, la navigation est plus optimale :

  • Les Server Components sont à nouveau rendus sur le serveur lors de la navigation
  • Les Client Components des layouts partagés conservent leur état pendant la navigation ; les composants de la page que tu quittes sont démontés par défaut
  • Les transitions de page sont plus fluides grâce au mécanisme de « navigation instantanée »

C'est comme les commutateurs quantiques avancés de Quantum Metropolis - ils permettent des transitions instantanées entre différents layouts sans interrompre le fonctionnement des systèmes.

Erreurs courantes et pièges

1. Utiliser des hooks React dans des Server Components

1// WRONG - Server Component trying to use React hooks
2export default function ServerComponent() {
3  // This will cause an error!
4  const [count, setCount] = useState(0);
5
6  return <div>Count: {count}</div>;
7}

2. Tenter d'accéder aux API du navigateur dans des Server Components

1// WRONG - Server Component trying to use browser APIs
2export default function ServerComponent() {
3  // This will cause an error! (ReferenceError: window is not defined)
4  const windowWidth = window.innerWidth;
5
6  return <div>Width: {windowWidth}px</div>;
7}

3. Passer des fonctions d'un Server Component à un Client Component

1// WRONG - Server Component passes a regular function to a Client Component
2// app/page.tsx (Server Component)
3import ClientButton from './client-button';
4
5export default function Page() {
6  const handleClick = () => console.log("Clicked!");
7
8  // This won't work! Props passed to a Client Component must be serializable
9  return <ClientButton onClick={handleClick} />;
10}

Les Server Actions font exception : tu peux passer une fonction marquée "use server" à un Client Component.

Patterns avancés pour utiliser Server et Client Components

1. Le pattern Provider

Si tu as besoin de partager un état entre différents Client Components, utilise un Client Provider à un niveau élevé. C'est similaire aux unités de synchronisation centrales de Quantum Metropolis, qui coordonnent les actions des différents appareils personnels.

1// providers.tsx - Client Component
2"use client";
3import { createContext, useState } from 'react';
4
5export const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });
6
7export function ThemeProvider({ children }) {
8  const [theme, setTheme] = useState('light');
9  const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light');
10
11  return (
12    <ThemeContext.Provider value={{ theme, toggleTheme }}>
13      {children}
14    </ThemeContext.Provider>
15  );
16}
1// layout.tsx - Server Component
2import { ThemeProvider } from './providers';
3
4export default function RootLayout({ children }: { children: React.ReactNode }) {
5  return (
6    <html>
7      <body>
8        <ThemeProvider>
9          {children}
10        </ThemeProvider>
11      </body>
12    </html>
13  );
14}

2. Le pattern « Lifting State Up »

Quand tu as plusieurs Client Components qui ont besoin d'accéder au même état, fais remonter l'état vers un ancêtre Client Component commun, et non vers un Server Component.

1// parent.tsx - Server Component
2export default function ParentServerComponent() {
3  return <ClientParent />;
4}
5
6// client-parent.tsx - Client Component (common ancestor)
7"use client";
8import { useState } from 'react';
9
10export function ClientParent() {
11  const [sharedState, setSharedState] = useState('initial');
12
13  return (
14    <div>
15      <ClientChildA sharedState={sharedState} setSharedState={setSharedState} />
16      <ClientChildB sharedState={sharedState} />
17    </div>
18  );
19}

3. Utilisation stratégique de Suspense

Utilise le composant Suspense pour gérer les états de chargement dans une application hybride :

1// page.tsx - Server Component
2import { Suspense } from 'react';
3
4export default function Page() {
5  return (
6    <div>
7      <h1>Quantum Metropolis System Data</h1>
8
9      <Suspense fallback={<div>Loading data...</div>}>
10        <AsyncDataComponent />
11      </Suspense>
12    </div>
13  );
14}
15
16// async-data-component.tsx - Server Component with asynchronous code
17async function AsyncDataComponent() {
18  // This component can be suspended during loading
19  const data = await fetchData();
20
21  return <div>{/* Rendering data */}</div>;
22}

Résumé

Le système de composants de Next.js 16 basé sur les React Server Components est un changement révolutionnaire qui combine les avantages du rendu côté serveur avec l'interactivité côté client. Tout comme à Quantum Metropolis, où la téléportation quantique et les véhicules personnels créent un système de transport hybride, dans Next.js les composants serveur et client travaillent ensemble, créant une interface efficace, évolutive et conviviale.

Lorsque tu conçois des applications Next.js 16 :

  1. Utilise les Server Components par défaut pour de meilleures performances et plus de sécurité
  2. Passe aux Client Components uniquement quand tu as besoin d'interactivité, de hooks ou des API du navigateur
  3. Découpe les composants de façon granulaire pour minimiser la quantité de JavaScript envoyée au client
  4. Souviens-toi du sens des props - du serveur vers le client (dans l'autre sens, tu envoies des données avec des Server Actions)
  5. Utilise les Server Actions pour le traitement des formulaires et d'autres actions utilisateur

Une bonne utilisation des Server et Client Components te permettra de créer des applications Next.js de nouvelle génération qui combinent chargement rapide, performance SEO et interfaces utilisateur riches - sans les compromis qui étaient nécessaires dans les applications React traditionnelles.

Dans la prochaine leçon, nous passerons à la pratique et créerons notre première application « Hello World » avec Next.js 16 et l'App Router, où nous appliquerons les connaissances acquises sur les Server et Client Components.

Code de cette leçon : App.tsx
1// Hooks - useState et useEffect - Metropolis Quantum
2import React, { useState, useEffect } from 'react';
3
4console.log("Hooks React - useState et useEffect");
5console.log("Gestion des systèmes quantiques\n");
6
7// ==========================================
8// 1. useState - gestion de l'état
9// ==========================================
10
11function QuantumReactor() {
12  const [temperature, setTemperature] = useState<number>(20);
13  const [pressure, setPressure] = useState<number>(100);
14  const [status, setStatus] = useState<'stable' | 'warning' | 'critical'>('stable');
15
16  // useEffect - surveille les changements de température et de pression
17  useEffect(() => {
18    console.log(`Température : ${temperature}°C, pression : ${pressure} kPa`);
19
20    // Détermine le statut à partir des valeurs
21    if (temperature > 80 || pressure > 150) {
22      setStatus('critical');
23    } else if (temperature > 60 || pressure > 120) {
24      setStatus('warning');
25    } else {
26      setStatus('stable');
27    }
28  }, [temperature, pressure]); // Appelé quand la température ou la pression change
29
30  const getStatusColor = () => {
31    switch(status) {
32      case 'stable': return '#4caf50';
33      case 'warning': return '#ff9800';
34      case 'critical': return '#f44336';
35    }
36  };
37
38  return (
39    <div style={{
40      background: 'rgba(255, 255, 255, 0.1)',
41      padding: '20px',
42      borderRadius: '12px',
43      border: `2px solid ${getStatusColor()}`
44    }}>
45      <h2 style={{ color: getStatusColor() }}>
46        Réacteur quantique - {status.toUpperCase()}
47      </h2>
48
49      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', marginTop: '20px' }}>
50        <div>
51          <h4>Température : {temperature}°C</h4>
52          <input
53            type="range"
54            min="0"
55            max="100"
56            value={temperature}
57            onChange={(e) => setTemperature(Number(e.target.value))}
58            style={{ width: '100%' }}
59          />
60        </div>
61
62        <div>
63          <h4>Pression : {pressure} kPa</h4>
64          <input
65            type="range"
66            min="50"
67            max="200"
68            value={pressure}
69            onChange={(e) => setPressure(Number(e.target.value))}
70            style={{ width: '100%' }}
71          />
72        </div>
73      </div>
74    </div>
75  );
76}
77
78// ==========================================
79// 2. useEffect - effets de bord
80// ==========================================
81
82function QuantumClock() {
83  const [time, setTime] = useState<Date>(new Date());
84  const [quantumTime, setQuantumTime] = useState<number>(0);
85
86  // useEffect pour l'horloge - s'exécute après le montage du composant
87  useEffect(() => {
88    console.log("Horloge quantique démarrée");
89
90    const interval = setInterval(() => {
91      setTime(new Date());
92      setQuantumTime(prev => prev + 1);
93    }, 1000);
94
95    // Fonction de nettoyage - appelée au démontage
96    return () => {
97      console.log("Horloge quantique arrêtée");
98      clearInterval(interval);
99    };
100  }, []); // Tableau vide = exécuté une seule fois après le montage
101
102  return (
103    <div style={{
104      background: 'rgba(124, 77, 255, 0.2)',
105      padding: '20px',
106      borderRadius: '12px',
107      textAlign: 'center'
108    }}>
109      <h3 style={{ color: '#7c4dff' }}>Portail temporel quantique</h3>
110      <div style={{ fontSize: '2rem', margin: '20px 0', color: '#64ffda' }}>
111        {time.toLocaleTimeString()}
112      </div>
113      <div style={{ color: '#b0bec5' }}>
114        Secondes quantiques : {quantumTime}
115      </div>
116    </div>
117  );
118}
119
120// ==========================================
121// 3. Plusieurs Hooks useEffect
122// ==========================================
123
124function DataMonitor() {
125  const [data, setData] = useState<number[]>([]);
126  const [average, setAverage] = useState<number>(0);
127  const [trend, setTrend] = useState<'up' | 'down' | 'stable'>('stable');
128
129  // Effet 1 : génère de nouvelles données chaque seconde
130  useEffect(() => {
131    const interval = setInterval(() => {
132      const newValue = Math.floor(Math.random() * 100);
133      setData(prev => [...prev.slice(-9), newValue]); // Garde les 10 dernières valeurs
134    }, 1000);
135
136    return () => clearInterval(interval);
137  }, []);
138
139  // Effet 2 : calcule la moyenne quand les données changent
140  useEffect(() => {
141    if (data.length > 0) {
142      const avg = data.reduce((a, b) => a + b, 0) / data.length;
143      setAverage(avg);
144      console.log(`Nouvelle moyenne : ${avg.toFixed(2)}`);
145    }
146  }, [data]);
147
148  // Effet 3 : détermine la tendance
149  useEffect(() => {
150    if (data.length >= 2) {
151      const lastTwo = data.slice(-2);
152      if (lastTwo[1] > lastTwo[0]) setTrend('up');
153      else if (lastTwo[1] < lastTwo[0]) setTrend('down');
154      else setTrend('stable');
155    }
156  }, [data]);
157
158  const getTrendIcon = () => {
159    switch(trend) {
160      case 'up': return '';
161      case 'down': return '';
162      case 'stable': return '';
163    }
164  };
165
166  return (
167    <div style={{
168      background: 'rgba(255, 255, 255, 0.1)',
169      padding: '20px',
170      borderRadius: '12px'
171    }}>
172      <h3 style={{ color: '#64ffda' }}>Moniteur de flux de données</h3>
173
174      <div style={{ display: 'flex', gap: '5px', marginTop: '15px', height: '100px', alignItems: 'flex-end' }}>
175        {data.map((value, index) => (
176          <div
177            key={index}
178            style={{
179              flex: 1,
180              height: `${value}%`,
181              background: 'linear-gradient(to top, #7c4dff, #64ffda)',
182              borderRadius: '4px 4px 0 0'
183            }}
184          />
185        ))}
186      </div>
187
188      <div style={{ marginTop: '20px', display: 'flex', justifyContent: 'space-around' }}>
189        <div>
190          <div style={{ color: '#b0bec5', fontSize: '0.9rem' }}>Moyenne</div>
191          <div style={{ color: '#64ffda', fontSize: '1.5rem', fontWeight: 'bold' }}>
192            {average.toFixed(1)}
193          </div>
194        </div>
195        <div>
196          <div style={{ color: '#b0bec5', fontSize: '0.9rem' }}>Tendance</div>
197          <div style={{ fontSize: '1.5rem' }}>
198            {getTrendIcon()}
199          </div>
200        </div>
201      </div>
202    </div>
203  );
204}
205
206// ==========================================
207// App principale
208// ==========================================
209function App() {
210  useEffect(() => {
211    console.log("Tableau de bord des systèmes quantiques initialisé");
212  }, []);
213
214  return (
215    <div style={{
216      padding: '20px',
217      background: 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%)',
218      minHeight: '100vh',
219      color: 'white'
220    }}>
221      <h1 style={{ textAlign: 'center', color: '#64ffda' }}>
222        Hooks React - useState et useEffect
223      </h1>
224
225      <div style={{ display: 'grid', gap: '20px', marginTop: '30px' }}>
226        <QuantumReactor />
227        <QuantumClock />
228        <DataMonitor />
229      </div>
230
231      <div style={{
232        marginTop: '30px',
233        background: 'rgba(100, 255, 218, 0.1)',
234        padding: '20px',
235        borderRadius: '10px',
236        borderLeft: '4px solid #64ffda'
237      }}>
238        <h3>Astuces useEffect :</h3>
239        <ul style={{ lineHeight: '1.8' }}>
240          <li>[] (tableau vide) = exécuté une seule fois après le montage</li>
241          <li>[dep1, dep2] = exécuté quand les dépendances changent</li>
242          <li>Sans tableau = exécuté après chaque rendu</li>
243          <li>Fonction retournée = nettoyage avant le démontage</li>
244          <li>Tu peux avoir plusieurs useEffect dans un composant</li>
245        </ul>
246      </div>
247    </div>
248  );
249}
250
251export default App;

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Les Props dans React sont :

  2. 2. Le State dans React est :

  3. 3. Le hook useState sert à :

Tâches pratiques dans le jeu

  • Éditeur de code

    Crée un Server Component asynchrone ImplantDashboard (export default). Au-dessus du composant, définis une fonction async getImplants() qui retourne un tableau de 3 implants de la forme { id, name, status }. Dans le composant, récupère les données avec const implants = await getImplants(), puis affiche-les dans un <ul> : chaque implant dans un <li key={implant.id}> avec son nom et son statut.

  • Éditeur de code

    Crée un Client Component NeuralVisualizer (export default) : ajoute 'use client' en haut du fichier, garde l'activité dans un useState (un booléen, false au départ), ajoute un bouton « Activer Neural Link » qui change l'état, et affiche « Statut : ACTIF » ou « Statut : INACTIF ».

  • Clique dans l'ordre

    Ordonne la syntaxe pour exporter un Server Component dans Next.js

  • Éditeur de code

    Crée un composant Greeting qui accepte une prop 'name' et affiche un message de bienvenue personnalisé

  • Tri vertical

    Ordonne les étapes du rendu SSR d'une page dans Next.js

  • Éditeur de code

    Crée un composant Counter qui affiche un compteur (useState, valeur initiale 0) et un bouton qui augmente le compteur de 1 à chaque clic.

  • Clique dans l'ordre

    Ordonne la syntaxe de déstructuration du hook useState

  • Éditeur de code

    Crée un composant HolographicInterface : exporte une fonction par défaut qui retourne une div avec la classe 'dashboard-panel', un titre h1 'Next.js Dashboard' et un paragraphe avec le statut du système.

  • Éditeur de code

    Crée un composant de page de connexion LoginPage (export default). Ajoute 'use client' en haut du fichier. Retourne un <form> avec un champ e-mail (<input type="email">), un champ mot de passe (<input type="password">) et un bouton « Se connecter » (<button type="submit">). Dans le gestionnaire onSubmit, appelle event.preventDefault() pour que la page ne se recharge pas.

Articles utiles