Kurs Next.js · Moduł 1: Konfiguracja Next.js

System komponentów - Client i Server Components

12 min czytania
W tej lekcji10

W Metropolii Quantum architekci systemów kwantowych zawsze muszą podejmować fundamentalne decyzje: które procesy uruchomić na centralnych procesorach kwantowych (dla zadań wymagających najwyższej mocy obliczeniowej), a które na urządzeniach końcowych obywateli (dla natychmiastowej interaktywności). Ta dychotomia doskonale odzwierciedla rewolucyjną zmianę, którą App Router wprowadził w Next.js 13, a Next.js 16 rozwija dalej - podział na Server Components i Client Components.

Rewolucja React Server Components

React Server Components (RSC) stanowią największą zmianę paradygmatu w React od czasu wprowadzenia hooków. Przed ich pojawieniem się wszystkie komponenty React były renderowane na serwerze lub kliencie jako spójna całość. Teraz możemy tworzyć aplikacje, w których niektóre komponenty są renderowane wyłącznie na serwerze, inne na kliencie, a dane i struktury UI płynnie między nimi przepływają.

To jak różnica między tradycyjnymi systemami transportowymi Metropolii Quantum, gdzie pojazdy poruszają się tylko po określonych trasach, a nowym systemem kwantowego transportu, gdzie materia może być teleportowana bezpośrednio do miejsca docelowego.

Server Components - podstawy

W App Routerze Next.js 16 wszystkie komponenty są domyślnie Server Components, chyba że jawnie oznaczymy je jako Client Components.

Server Components są renderowane na serwerze i nie wysyłają żadnego JavaScript do przeglądarki. To jak kwantowe procesory obliczeniowe Metropolii Quantum, które wykonują skomplikowane obliczenia i przesyłają tylko końcowe wyniki do urządzeń obywateli.

Zalety Server Components:

  1. Dostęp do zasobów serwera - bezpośredni dostęp do baz danych, systemu plików i innych zasobów serwera
  2. Bezpieczeństwo - wrażliwe dane i logika pozostają na serwerze
  3. Automatyczny code splitting - tylko niezbędny kod jest wysyłany do klienta
  4. Mniejszy rozmiar paczek JavaScript - brak potrzeby przesyłania kodu do renderowania
  5. Lepsze SEO - szybsze renderowanie statyczne
  6. Lepsza wydajność pierwszego ładowania - mniejsza ilość JavaScript do pobrania i wykonania

Przykład Server Component:

1// app/dashboard/stats/page.tsx - Server Component (domyślnie)
2export default async function StatsPage() {
3  // Ten kod wykonuje się TYLKO na serwerze
4  const stats = await fetchQuantumSystemStats();
5
6  return (
7    <section className="quantum-stats">
8      <h1>Statystyki Systemów Kwantowych</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}

Zwróć uwagę na kilka ważnych aspektów:

  1. Użycie async/await bezpośrednio w komponencie - to możliwe tylko w Server Components
  2. Brak użycia hooków React (useState, useEffect itp.) - nie są one dostępne w Server Components
  3. Brak obsługi zdarzeń - Server Components nie mogą obsługiwać zdarzeń użytkownika

Client Components - podstawy

Client Components zapewniają interaktywność: przy pierwszym wejściu Next.js prerenderuje je do HTML na serwerze, a w przeglądarce są hydratowane i dalej działają po stronie klienta. Aby oznaczyć komponent jako Client Component, dodajemy na górze pliku dyrektywę "use client".

Ten proces nazywa się hydratacją: React w przeglądarce podłącza obsługę zdarzeń do HTML-a wyrenderowanego wcześniej na serwerze. Dopiero po hydratacji przyciski reagują na kliknięcia, a efekty z useEffect uruchamiają się po niej.

To jak osobiste urządzenia kwantowe obywateli Metropolii Quantum - lżejsze, ale natychmiastowo reagujące na akcje użytkownika.

Zalety Client Components:

  1. Interaktywność - obsługa zdarzeń użytkownika (kliknięcia, wprowadzanie tekstu itp.)
  2. Hooki React - możliwość używania useState, useEffect, useContext i innych hooków
  3. Dostęp do API przeglądarki - window, document, localStorage itp.
  4. Efekty po stronie klienta - animacje, interakcje w czasie rzeczywistym

Przykład Client Component:

1// app/dashboard/interactive-controls.tsx
2"use client"; // Ta dyrektywa oznacza komponent kliencki
3
4import { useState, useEffect } from 'react';
5
6export default function InteractiveControls() {
7  // Ten kod wykonuje się w przeglądarce użytkownika
8  const [activeSystem, setActiveSystem] = useState('energy');
9  const [status, setStatus] = useState('idle');
10
11  useEffect(() => {
12    // Efekty działają tylko na kliencie
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          Energia
30        </button>
31        <button onClick={() => setActiveSystem('security')}>
32          Bezpieczeństwo
33        </button>
34      </div>
35    </div>
36  );
37}

Kluczowe aspekty:

  1. Dyrektywa "use client" na początku pliku
  2. Używanie hooków React (useState, useEffect)
  3. Obsługa zdarzeń (onClick)

Łączenie Server i Client Components

Najpotężniejszą cechą nowego modelu jest możliwość łączenia Server i Client Components w jednej aplikacji. Server Components mogą renderować Client Components, ale nie odwrotnie (z pewnymi wyjątkami przy użyciu wzorców). Jest to jak hybrydowy system transportu Metropolii Quantum, gdzie główne trasy są obsługiwane przez teleportację kwantową, a ostatni odcinek przez osobiste pojazdy.

Przykład łączenia komponentów:

1// app/dashboard/page.tsx - Server Component
2export default async function DashboardPage() {
3  // Ten kod wykonuje się TYLKO na serwerze
4  const dashboardData = await fetchDashboardData();
5
6  return (
7    <main className="quantum-dashboard">
8      <h1>Panel Kontrolny Metropolii Quantum</h1>
9
10      {/* Server Component */}
11      <SystemStatusPanel systemsData={dashboardData.systems} />
12
13      {/* Client Component (importowany i używany w Server Component) */}
14      <InteractiveControls />
15    </main>
16  );
17}
1// app/dashboard/system-status-panel.tsx - Server Component (domyślnie)
2export default function SystemStatusPanel({ systemsData }) {
3  return (
4    <div className="system-status-panel">
5      <h2>Status Systemów</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 (domyślnie)
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>Wydajność: {system.performance}%</p>
9
10      {/* Client Component używany w 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' ? 'Aktywny' : 'Nieaktywny'}
20    </div>
21  );
22}

Strategie optymalizacji przy projektowaniu komponentów

Właściwe decydowanie, które komponenty powinny być Server Components, a które Client Components, jest kluczowe dla wydajności aplikacji. Oto kilka strategii, które pomogą Ci podejmować te decyzje:

1. Server Component jako domyślny wybór

Zawsze zaczynaj od Server Components, chyba że potrzebujesz funkcji dostępnych tylko w Client Components. To podobne do podejścia w Metropolii Quantum, gdzie domyślnie używa się centralnych jednostek obliczeniowych, chyba że potrzebna jest natychmiastowa interaktywność.

2. Granularna interaktywność

Zamiast oznaczać cały duży komponent jako "use client", wydziel tylko interaktywne części do osobnych Client Components, pozostawiając resztę jako Server Components.

Przed optymalizacją:

1// MNIEJ OPTYMALNE - cały komponent jest 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>Panel Sterowania</h2>
11      <p>Ten panel pozwala na zarządzanie systemami kwantowymi.</p>
12
13      <div className="tabs">
14        <button onClick={() => setActiveTab('overview')}>Przegląd</button>
15        <button onClick={() => setActiveTab('settings')}>Ustawienia</button>
16      </div>
17
18      <div className="tab-content">
19        {activeTab === 'overview' ? (
20          <OverviewContent /> // Dużo statycznej zawartości
21        ) : (
22          <SettingsContent /> // Dużo statycznej zawartości
23        )}
24      </div>
25    </div>
26  );
27}

Po optymalizacji:

1// LEPSZE - rozdzielenie na Client i 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>Panel Sterowania</h2>
12      <p>Ten panel pozwala na zarządzanie systemami kwantowymi.</p>
13
14      {/* Tylko TabController jest Client Component; zawartość zakładek zostaje na serwerze */}
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')}>Przegląd</button>
31        <button onClick={() => setActiveTab('settings')}>Ustawienia</button>
32      </div>
33
34      <div className="tab-content">
35        {activeTab === 'overview' ? overview : settings}
36      </div>
37    </>
38  );
39}

Komponent zaimportowany w pliku z "use client" też staje się Client Component. Dlatego OverviewContent i SettingsContent importuje Server Component ControlPanel i przekazuje je do TabController jako propsy, tak jak children.

3. Wzorzec "Kontener i Prezentacja"

Użyj Server Components jako "kontenerów" do pobierania danych i organizacji, a Client Components jako "komponentów prezentacyjnych" odpowiedzialnych za interaktywność.

1// products-container.tsx - Server Component (kontener)
2export default async function ProductsContainer() {
3  const products = await fetchProducts();
4
5  return <ProductsList products={products} />;
6}
7
8// products-list.tsx - Client Component (prezentacja)
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')}>Sortuj po nazwie</button>
22        <button onClick={() => setSortBy('price')}>Sortuj po cenie</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}

Przepływ danych między Server i Client Components

Przesyłanie danych między serwerem a klientem wymaga specjalnego podejścia. W modelu RSC props płyną w drzewie komponentów z serwera do klienta; w drugą stronę klient wysyła dane, wywołując Server Actions (poznasz je za chwilę). To jak kanały kwantowe w Metropolii Quantum - rozkazy płyną z centrali do urządzeń osobistych, a meldunki wracają osobnym kanałem.

Przekazywanie danych do Client Components:

1// server-component.tsx - Server Component
2async function ServerComponent() {
3  const data = await fetchSensitiveData();
4
5  // Przekazanie przetworzonych danych do klienta
6  return <ClientComponent processedData={data.publicPart} />;
7}
8
9// client-component.tsx - Client Component
10"use client";
11
12export function ClientComponent({ processedData }) {
13  // Używanie danych przekazanych z serwera
14  return <div>{processedData.map(item => <p key={item.id}>{item.name}</p>)}</div>;
15}

Obsługa akcji użytkownika z Client Components:

Gdy Client Component musi wykonać akcję, która angażuje serwer, używamy Server Actions albo Route Handlers (plików route.ts; w Pages Routerze ich odpowiednikiem są API Routes).

Z użyciem Server Actions (stabilnych od Next.js 14):

1// actions.js - Server Actions
2"use server";
3
4export async function updateSystemStatus(systemId, newStatus) {
5  // Ta funkcja działa na serwerze
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' ? 'Dezaktywuj' : 'Aktywuj'}
30    </button>
31  );
32}

Różnice w przeładowaniach i nawigacji

W tradycyjnym modelu React przeładowanie strony lub nawigacja często oznaczały ponowne uruchomienie całej aplikacji. W modelu React Server Components nawigacja jest wydajniejsza:

  • Server Components są ponownie renderowane na serwerze podczas nawigacji
  • Client Components we wspólnych layoutach zachowują stan podczas nawigacji; komponenty opuszczanej strony są domyślnie odmontowywane
  • Przejścia między stronami są płynniejsze dzięki mechanizmowi "instant navigation"

Jest to jak zaawansowane przełączniki kwantowe w Metropolii Quantum - pozwalają na natychmiastowe przejścia między różnymi układami bez przerywania działania systemów.

Typowe błędy i pułapki

1. Użycie hooków React w Server Components

1// ŹLE - Server Component próbuje używać hooków React
2export default function ServerComponent() {
3  // To spowoduje błąd!
4  const [count, setCount] = useState(0);
5
6  return <div>Count: {count}</div>;
7}

2. Próba dostępu do API przeglądarki w Server Components

1// ŹLE - Server Component próbuje używać API przeglądarki
2export default function ServerComponent() {
3  // To spowoduje błąd! (ReferenceError: window is not defined)
4  const windowWidth = window.innerWidth;
5
6  return <div>Width: {windowWidth}px</div>;
7}

3. Przekazywanie funkcji z Server Components do Client Components

1// ŹLE - Server Component przekazuje zwykłą funkcję do 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  // To nie zadziała! Props przekazywane do Client Component muszą dać się zserializować
9  return <ClientButton onClick={handleClick} />;
10}

Wyjątkiem są Server Actions: funkcję oznaczoną "use server" możesz przekazać do Client Component.

Zaawansowane wzorce używania Server i Client Components

1. Wzorzec Providera

Jeśli potrzebujesz współdzielić stan między różnymi Client Components, używaj Client Providera na wysokim poziomie. Jest to podobne do centralnych jednostek synchronizacyjnych w Metropolii Quantum, które koordynują działania różnych urządzeń osobistych.

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. Wzorzec "podnoszenia stanu" (lifting state up)

Kiedy masz kilka Client Components, które potrzebują dostępu do tego samego stanu, podnieś stan do wspólnego przodka Client Component, a nie do Server Component.

1// parent.tsx - Server Component
2export default function ParentServerComponent() {
3  return <ClientParent />;
4}
5
6// client-parent.tsx - Client Component (wspólny przodek)
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. Strategiczne użycie suspense

Użyj komponentu Suspense do zarządzania stanami ładowania w aplikacji hybrydowej:

1// page.tsx - Server Component
2import { Suspense } from 'react';
3
4export default function Page() {
5  return (
6    <div>
7      <h1>Dane systemowe Metropolii Quantum</h1>
8
9      <Suspense fallback={<div>Ładowanie danych...</div>}>
10        <AsyncDataComponent />
11      </Suspense>
12    </div>
13  );
14}
15
16// async-data-component.tsx - Server Component z asynchronicznym kodem
17async function AsyncDataComponent() {
18  // Ten komponent może być zawieszony (suspendowany) podczas ładowania
19  const data = await fetchData();
20
21  return <div>{/* Renderowanie danych */}</div>;
22}

Podsumowanie

System komponentów w Next.js 16 oparty na React Server Components to rewolucyjna zmiana, która łączy zalety renderowania serwerowego z interaktywnością kliencką. Podobnie jak w Metropolii Quantum, gdzie kwantowa teleportacja i osobiste pojazdy tworzą hybrydowy system transportu, tak w Next.js komponenty serwerowe i klienckie współpracują, tworząc wydajny, skalowalny i przyjazny dla użytkownika interfejs.

Przy projektowaniu aplikacji Next.js 16:

  1. Domyślnie używaj Server Components dla lepszej wydajności i bezpieczeństwa
  2. Przełącz na Client Components tylko wtedy, gdy potrzebujesz interaktywności, hooków lub API przeglądarki
  3. Rozdzielaj komponenty granularnie, aby zminimalizować ilość kodu JavaScript wysyłanego do klienta
  4. Pamiętaj o kierunku przepływu props - z serwera do klienta (dane w drugą stronę wysyłasz przez Server Actions)
  5. Używaj Server Actions do przetwarzania formularzy i innych akcji użytkownika

Prawidłowe wykorzystanie Server i Client Components pozwoli Ci tworzyć aplikacje Next.js nowej generacji, które łączą szybkość ładowania, wydajność SEO i bogaty interfejs użytkownika - bez kompromisów, które były konieczne w tradycyjnych aplikacjach React.

W następnej lekcji przejdziemy do praktyki i stworzymy naszą pierwszą aplikację "Hello World" z użyciem Next.js 16 i App Routera, gdzie zastosujemy zdobytą wiedzę o Server i Client Components.

Kod do tej lekcji: App.tsx
1// Hooki - useState i useEffect - Metropolis Quantum
2import React, { useState, useEffect } from 'react';
3
4console.log("Hooki Reacta - useState i useEffect");
5console.log("Zarządzanie systemami kwantowymi\n");
6
7// ==========================================
8// 1. useState - Zarządzanie Stanem
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 - monitoruj zmiany temperatury i ciśnienia
17  useEffect(() => {
18    console.log(`Temperatura: ${temperature}°C, Ciśnienie: ${pressure} kPa`);
19
20    // Określ status na podstawie wartości
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]); // Wywołaj, gdy zmieni się temperatura lub ciśnienie
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        Reaktor kwantowy - {status.toUpperCase()}
47      </h2>
48
49      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', marginTop: '20px' }}>
50        <div>
51          <h4>Temperatura: {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>Ciśnienie: {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 - efekty uboczne
80// ==========================================
81
82function QuantumClock() {
83  const [time, setTime] = useState<Date>(new Date());
84  const [quantumTime, setQuantumTime] = useState<number>(0);
85
86  // useEffect dla zegara - uruchamia się po zamontowaniu komponentu
87  useEffect(() => {
88    console.log("Zegar kwantowy uruchomiony");
89
90    const interval = setInterval(() => {
91      setTime(new Date());
92      setQuantumTime(prev => prev + 1);
93    }, 1000);
94
95    // Funkcja sprzątająca - wywołana przy odmontowaniu
96    return () => {
97      console.log("Zegar kwantowy zatrzymany");
98      clearInterval(interval);
99    };
100  }, []); // Pusta tablica = uruchom raz po zamontowaniu
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' }}>Kwantowy portal czasu</h3>
110      <div style={{ fontSize: '2rem', margin: '20px 0', color: '#64ffda' }}>
111        {time.toLocaleTimeString()}
112      </div>
113      <div style={{ color: '#b0bec5' }}>
114        Sekundy kwantowe: {quantumTime}
115      </div>
116    </div>
117  );
118}
119
120// ==========================================
121// 3. Kilka hooków 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  // Efekt 1: generuj nowe dane co sekundę
130  useEffect(() => {
131    const interval = setInterval(() => {
132      const newValue = Math.floor(Math.random() * 100);
133      setData(prev => [...prev.slice(-9), newValue]); // Zachowaj ostatnie 10 wartości
134    }, 1000);
135
136    return () => clearInterval(interval);
137  }, []);
138
139  // Efekt 2: oblicz średnią, gdy dane się zmienią
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(`Nowa średnia: ${avg.toFixed(2)}`);
145    }
146  }, [data]);
147
148  // Efekt 3: określ trend
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' }}>Monitor strumienia danych</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' }}>Średnia</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' }}>Trend</div>
197          <div style={{ fontSize: '1.5rem' }}>
198            {getTrendIcon()}
199          </div>
200        </div>
201      </div>
202    </div>
203  );
204}
205
206// ==========================================
207// Główna aplikacja
208// ==========================================
209function App() {
210  useEffect(() => {
211    console.log("Panel systemów kwantowych zainicjalizowany");
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        Hooki Reacta - useState i 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>Wskazówki do useEffect:</h3>
239        <ul style={{ lineHeight: '1.8' }}>
240          <li>[] (pusta tablica) = uruchom raz po zamontowaniu</li>
241          <li>[dep1, dep2] = uruchom, gdy zmienią się zależności</li>
242          <li>Bez tablicy = uruchom po każdym renderze</li>
243          <li>Zwracana funkcja = sprzątanie przed odmontowaniem</li>
244          <li>Możesz mieć wiele useEffect w komponencie</li>
245        </ul>
246      </div>
247    </div>
248  );
249}
250
251export default App;

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Props w React to:

  2. 2. State w React to:

  3. 3. Hook useState służy do:

Zadania praktyczne w grze

  • Edytor kodu

    Stwórz async Server Component ImplantDashboard (export default). Nad komponentem zdefiniuj funkcję async getImplants(), która zwraca tablicę 3 implantów w postaci { id, name, status }. W komponencie pobierz dane: const implants = await getImplants(), a potem wyświetl je w <ul>: każdy implant jako <li key={implant.id}> z nazwą i statusem.

  • Edytor kodu

    Stwórz Client Component NeuralVisualizer (export default): na początku pliku dodaj 'use client', aktywność trzymaj w useState (boolean, początkowo false), dodaj przycisk „Aktywuj Neural Link”, który zmienia stan, i wyświetl „Status: AKTYWNY” albo „Status: NIEAKTYWNY”.

  • Klikanie w kolejności

    Ułóż składnię eksportu Server Component w Next.js

  • Edytor kodu

    Utwórz komponent Greeting, który przyjmuje prop 'name' i wyświetla spersonalizowane powitanie

  • Układanie w pionie

    Ułóż kolejność kroków renderowania strony SSR w Next.js

  • Edytor kodu

    Utwórz komponent Counter, który wyświetla licznik (useState, wartość początkowa 0) i przycisk zwiększający licznik o 1 przy każdym kliknięciu.

  • Klikanie w kolejności

    Ułóż składnię destrukturyzacji hooka useState

  • Edytor kodu

    Stwórz komponent HolographicInterface: eksportuj domyślną funkcję zwracającą div z klasą 'dashboard-panel', nagłówkiem h1 'Next.js Dashboard' i paragrafem ze statusem systemu.

  • Edytor kodu

    Stwórz komponent strony logowania LoginPage (export default). Na początku pliku dodaj 'use client'. Zwróć formularz <form> z polem e-mail (<input type="email">), polem hasła (<input type="password">) i przyciskiem „Zaloguj” (<button type="submit">). W obsłudze onSubmit wywołaj event.preventDefault(), żeby strona się nie przeładowała.

Przydatne artykuły