Kurs JavaScript i React · Moduł 3: Stan i zdarzenia

Obsługa zdarzeń

10 min czytania
W tej lekcji8

W kosmicznej podróży przez React, obsługa zdarzeń to jak system kontroli lotu, pozwalający na reagowanie na różne akcje użytkownika. Jest to kluczowy element tworzenia interaktywnych interfejsów, które odpowiadają na działania użytkownika, takie jak kliknięcia, wprowadzanie tekstu czy ruchy myszką.

Podstawy obsługi zdarzeń w React

W React, obsługa zdarzeń działa podobnie jak w standardowym HTML, ale z kilkoma istotnymi różnicami:

  1. Nazwy zdarzeń używają konwencji camelCase zamiast małych liter:

    • HTML: onclick, onmouseover
    • React: onClick, onMouseOver
  2. W JSX przekazujemy funkcję jako handler zdarzenia, a nie string:

    • HTML: <button onclick="handleClick()">Kliknij</button>
    • React: <button onClick={handleClick}>Kliknij</button>
  3. Aby zapobiec domyślnemu zachowaniu, musimy jawnie wywołać preventDefault:

    • HTML: <a href="#" onclick="return false">Kliknij</a>
    • React: <a href="#" onClick={(e) => e.preventDefault()}>Kliknij</a>

Reagowanie na działania użytkownika

Zdarzenia to sposób, w jaki komponenty React reagują na działania użytkownika. Przyjrzyjmy się podstawowym typom zdarzeń i jak je obsługiwać.

Obsługa kliknięć

Najprostszym i najczęściej używanym zdarzeniem jest onClick:

1function LaunchButton() {
2  const handleLaunch = () => {
3    console.log('Rozpoczynamy odliczanie do startu!');
4    // Logika startu rakiety...
5  };
6
7  return (
8    <button
9      className="launch-button"
10      onClick={handleLaunch}
11    >
12      Rozpocznij odliczanie
13    </button>
14  );
15}

Argumenty w handlerach zdarzeń

Każdy handler zdarzenia otrzymuje obiekt zdarzenia (event) jako pierwszy argument. Zawiera on informacje o zdarzeniu i oferuje metody takie jak preventDefault():

1function NavigationLink() {
2  const handleClick = (event) => {
3    event.preventDefault(); // Zapobiega domyślnej akcji (np. przejściu do innej strony)
4    console.log('Link kliknięty, ale strona się nie przeładowała');
5
6    // Niestandardowa nawigacja lub inna logika...
7  };
8
9  return (
10    <a
11      href="/destination"
12      onClick={handleClick}
13    >
14      Przejdź do celu podróży
15    </a>
16  );
17}

Obiekt, który React przekazuje do handlera, to SyntheticEvent - opakowanie natywnego zdarzenia przeglądarki. Ma ten sam interfejs (target, preventDefault(), stopPropagation()) i działa tak samo w każdej przeglądarce, a oryginalne zdarzenie znajdziesz w event.nativeEvent. Kolejność jest więc taka: użytkownik klika, przeglądarka tworzy zdarzenie, React opakowuje je w SyntheticEvent i dopiero wtedy wywołuje twój handler.

Przekazywanie argumentów do handlerów zdarzeń

Czasami potrzebujemy przekazać dodatkowe argumenty do handlera zdarzenia. Możemy to zrobić na dwa sposoby:

1. Używając funkcji strzałkowej (arrow function)

1function MissionControl({ missions }) {
2  const selectMission = (missionId, event) => {
3    console.log(`Wybrano misję o ID: ${missionId}`);
4    console.log('Zdarzenie:', event);
5    // Logika wyboru misji...
6  };
7
8  return (
9    <div className="mission-control">
10      <h2>Dostępne misje</h2>
11      <ul>
12        {missions.map(mission => (
13          <li key={mission.id}>
14            {mission.name}
15            <button onClick={(e) => selectMission(mission.id, e)}>
16              Wybierz
17            </button>
18          </li>
19        ))}
20      </ul>
21    </div>
22  );
23}

2. Używając bind

1function MissionControl({ missions }) {
2  const selectMission = (missionId, event) => {
3    console.log(`Wybrano misję o ID: ${missionId}`);
4    // Logika wyboru misji...
5  };
6
7  return (
8    <div className="mission-control">
9      <h2>Dostępne misje</h2>
10      <ul>
11        {missions.map(mission => (
12          <li key={mission.id}>
13            {mission.name}
14            <button onClick={selectMission.bind(null, mission.id)}>
15              Wybierz
16            </button>
17          </li>
18        ))}
19      </ul>
20    </div>
21  );
22}

Podejście z funkcją strzałkową jest bardziej powszechne i zazwyczaj czytelniejsze.

Popularne zdarzenia w React

React obsługuje szeroki zakres zdarzeń, które możemy podzielić na kilka kategorii:

Zdarzenia myszy

  • onClick - kliknięcie
  • onDoubleClick - podwójne kliknięcie
  • onMouseEnter - najechanie kursorem na element
  • onMouseLeave - wyjście kursorem poza element
  • onMouseMove - ruch kursora nad elementem

Zdarzenia formularzy

  • onChange - zmiana wartości elementu formularza
  • onSubmit - wysłanie formularza
  • onFocus - element otrzymuje focus
  • onBlur - element traci focus

Zdarzenia klawiatury

  • onKeyDown - wciśnięcie klawisza
  • onKeyPress - przestarzałe, zamiast niego używaj onKeyDown
  • onKeyUp - zwolnienie klawisza

Zdarzenia dotyku (dla urządzeń mobilnych)

  • onTouchStart - rozpoczęcie dotyku
  • onTouchMove - ruch podczas dotyku
  • onTouchEnd - zakończenie dotyku

Przykłady obsługi różnych zdarzeń

Zdarzenia myszy ze stanem

Stan zapamiętuje, czy kursor jest nad panelem i czy panel został kliknięty, a zdarzenia onMouseEnter, onMouseLeave i onClick tylko ten stan zmieniają:

1function InteractivePanel() {
2  const [isHovered, setIsHovered] = useState(false);
3  const [isClicked, setIsClicked] = useState(false);
4
5  return (
6    <div
7      className={`panel ${isHovered ? 'hovered' : ''} ${isClicked ? 'clicked' : ''}`}
8      onMouseEnter={() => setIsHovered(true)}
9      onMouseLeave={() => {
10        setIsHovered(false);
11        setIsClicked(false);
12      }}
13      onClick={() => setIsClicked(!isClicked)}
14    >
15      <h3>Panel interaktywny</h3>
16      <p>
17        {isHovered ? 'Kursor nad panelem' : 'Najedź na panel'}
18        {isClicked ? ' - Panel aktywny' : ' - Kliknij, aby aktywować'}
19      </p>
20    </div>
21  );
22}

Zdarzenia klawiatury

Klawisze obsługujemy tak samo jak kliknięcia - przez onKeyDown na elemencie, a event.key mówi, który klawisz wciśnięto:

1function SpaceshipControls() {
2  const [position, setPosition] = useState({ x: 50, y: 50 });
3
4  const handleKeyDown = (event) => {
5    switch(event.key) {
6      case 'ArrowUp':
7        setPosition(prev => ({ ...prev, y: Math.max(0, prev.y - 5) }));
8        break;
9      case 'ArrowDown':
10        setPosition(prev => ({ ...prev, y: Math.min(100, prev.y + 5) }));
11        break;
12      case 'ArrowLeft':
13        setPosition(prev => ({ ...prev, x: Math.max(0, prev.x - 5) }));
14        break;
15      case 'ArrowRight':
16        setPosition(prev => ({ ...prev, x: Math.min(100, prev.x + 5) }));
17        break;
18      default:
19        return;
20    }
21  };
22
23  return (
24    <div className="spaceship-simulator" tabIndex={0} onKeyDown={handleKeyDown}>
25      <h2>Symulator statku kosmicznego</h2>
26      <p>Kliknij symulator i steruj statkiem strzałkami</p>
27
28      <div className="universe" style={{ position: 'relative', width: '400px', height: '400px', background: 'black' }}>
29        <div
30          className="spaceship"
31          style={{
32            position: 'absolute',
33            left: `${position.x}%`,
34            top: `${position.y}%`,
35            transform: 'translate(-50%, -50%)',
36            width: '20px',
37            height: '20px',
38            background: 'red'
39          }}
40        ></div>
41      </div>
42
43      <div className="coordinates">
44        <p>Pozycja X: {position.x}</p>
45        <p>Pozycja Y: {position.y}</p>
46      </div>
47    </div>
48  );
49}

tabIndex={0} pozwala elementowi div przyjąć fokus, więc onKeyDown działa po kliknięciu w symulator. Nasłuch na całym dokumencie wymaga useEffect ze sprzątaniem - poznasz go w module o efektach.

Przekazywanie funkcji jako props

Często potrzebujemy przekazać funkcje obsługi zdarzeń z komponentu rodzica do komponentu potomnego. Jest to powszechna praktyka, która pozwala na komunikację między komponentami:

1function MissionControl({ onLaunch, onAbort }) {
2  const [countdown, setCountdown] = useState(10);
3  const [isLaunching, setIsLaunching] = useState(false);
4  // useRef przechowuje identyfikator timera między renderami (zmiana ref nie powoduje ponownego renderowania)
5  const timerRef = useRef(null);
6
7  const stopTimer = () => {
8    clearInterval(timerRef.current);
9    timerRef.current = null;
10  };
11
12  const startCountdown = () => {
13    setIsLaunching(true);
14    let secondsLeft = 10;
15    setCountdown(secondsLeft);
16
17    timerRef.current = setInterval(() => {
18      secondsLeft -= 1;
19      setCountdown(secondsLeft);
20      if (secondsLeft === 0) {
21        stopTimer();
22        setIsLaunching(false);
23        onLaunch(); // Wywołanie funkcji z props po zakończeniu odliczania (poza funkcją aktualizującą stan)
24      }
25    }, 1000);
26  };
27
28  const abortLaunch = () => {
29    stopTimer(); // Najpierw zatrzymaj odliczanie
30    setIsLaunching(false);
31    setCountdown(10);
32    onAbort(); // Wywołanie funkcji z props przy przerwaniu
33  };
34
35  return (
36    <div className="mission-control">
37      <h2>Centrum kontroli misji</h2>
38      <div className="countdown">
39        {isLaunching
40          ? `Start za: ${countdown}`
41          : 'Gotowy do startu'}
42      </div>
43
44      <div className="control-buttons">
45        {!isLaunching ? (
46          <button onClick={startCountdown}>Rozpocznij odliczanie</button>
47        ) : (
48          <button onClick={abortLaunch} className="abort">Przerwij start</button>
49        )}
50      </div>
51    </div>
52  );
53}
54
55// Użycie komponentu
56function SpaceProgram() {
57  const handleLaunch = () => {
58    console.log('Rakieta wystartowała!');
59    // Logika po starcie...
60  };
61
62  const handleAbort = () => {
63    console.log('Start przerwany!');
64    // Logika po przerwaniu...
65  };
66
67  return (
68    <div className="space-program">
69      <h1>Program Kosmiczny</h1>
70      <MissionControl
71        onLaunch={handleLaunch}
72        onAbort={handleAbort}
73      />
74    </div>
75  );
76}

Funkcja przekazana do setState (aktualizacja funkcyjna) musi być czysta: React w trybie deweloperskim (StrictMode) wywołuje ją dwa razy, więc wywołanie w niej onLaunch() uruchomiłoby rakietę dwukrotnie. Dlatego sekundy odliczamy w zwykłej zmiennej secondsLeft, a onLaunch() i onAbort() wywołujemy bezpośrednio w kodzie timera i handlera. Identyfikator timera trzymamy w useRef, żeby abortLaunch mógł go zatrzymać - useRef poznasz dokładniej przy komponentach niekontrolowanych w lekcji o przepływie danych.

Obsługa formularzy

Formularze są kluczowym elementem interakcji z użytkownikiem w aplikacjach webowych. React oferuje dwa podejścia do obsługi formularzy: kontrolowane i niekontrolowane komponenty.

Formularze kontrolowane

W kontrolowanych komponentach, dane formularza są kontrolowane przez stan React:

1function MissionSetup() {
2  const [missionData, setMissionData] = useState({
3    name: '',
4    destination: 'Moon',
5    crew: 3,
6    duration: 7
7  });
8
9  const handleChange = (e) => {
10    const { name, value } = e.target;
11    setMissionData({
12      ...missionData,
13      [name]: value
14    });
15  };
16
17  const handleSubmit = (e) => {
18    e.preventDefault();
19    console.log('Dane misji:', missionData);
20    // Logika wysyłania danych...
21  };
22
23  return (
24    <div className="mission-setup">
25      <h2>Konfiguracja misji</h2>
26
27      <form onSubmit={handleSubmit}>
28        <div className="form-group">
29          <label htmlFor="name">Nazwa misji:</label>
30          <input
31            type="text"
32            id="name"
33            name="name"
34            value={missionData.name}
35            onChange={handleChange}
36            required
37          />
38        </div>
39
40        <div className="form-group">
41          <label htmlFor="destination">Cel podróży:</label>
42          <select
43            id="destination"
44            name="destination"
45            value={missionData.destination}
46            onChange={handleChange}
47          >
48            <option value="Moon">Księżyc</option>
49            <option value="Mars">Mars</option>
50            <option value="ISS">Międzynarodowa Stacja Kosmiczna</option>
51            <option value="Asteroid">Pas asteroid</option>
52          </select>
53        </div>
54
55        <div className="form-group">
56          <label htmlFor="crew">Liczba załogantów:</label>
57          <input
58            type="number"
59            id="crew"
60            name="crew"
61            min="1"
62            max="10"
63            value={missionData.crew}
64            onChange={handleChange}
65          />
66        </div>
67
68        <div className="form-group">
69          <label htmlFor="duration">Czas trwania (dni):</label>
70          <input
71            type="number"
72            id="duration"
73            name="duration"
74            min="1"
75            value={missionData.duration}
76            onChange={handleChange}
77          />
78        </div>
79
80        <button type="submit">Zaplanuj misję</button>
81      </form>
82    </div>
83  );
84}

W powyższym przykładzie:

  1. Każde pole formularza ma swój odpowiednik w stanie
  2. Wartość każdego pola jest kontrolowana przez stan
  3. Zdarzenie onChange aktualizuje stan, co z kolei aktualizuje wartość pola
  4. Zdarzenie onSubmit jest używane do obsługi przesyłania formularza

Obsługa wielu typów pól formularza

Różne pola formularza wymagają nieco innego podejścia:

1function AstronautRegistration() {
2  const [formData, setFormData] = useState({
3    name: '',
4    age: '',
5    specialty: 'pilot',
6    experience: '',
7    isTrainee: false,
8    bio: '',
9    preferredShift: 'morning'
10  });
11
12  const handleChange = (e) => {
13    const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
14    setFormData({
15      ...formData,
16      [e.target.name]: value
17    });
18  };
19
20  const handleSubmit = (e) => {
21    e.preventDefault();
22    console.log('Dane astronauty:', formData);
23    // Logika rejestracji...
24  };
25
26  return (
27    <div className="astronaut-registration">
28      <h2>Rejestracja astronauty</h2>
29
30      <form onSubmit={handleSubmit}>
31        {/* Pole tekstowe */}
32        <div className="form-group">
33          <label htmlFor="name">Imię i nazwisko:</label>
34          <input
35            type="text"
36            id="name"
37            name="name"
38            value={formData.name}
39            onChange={handleChange}
40            required
41          />
42        </div>
43
44        {/* Pole liczbowe */}
45        <div className="form-group">
46          <label htmlFor="age">Wiek:</label>
47          <input
48            type="number"
49            id="age"
50            name="age"
51            value={formData.age}
52            onChange={handleChange}
53            required
54          />
55        </div>
56
57        {/* Select */}
58        <div className="form-group">
59          <label htmlFor="specialty">Specjalizacja:</label>
60          <select
61            id="specialty"
62            name="specialty"
63            value={formData.specialty}
64            onChange={handleChange}
65          >
66            <option value="pilot">Pilot</option>
67            <option value="engineer">Inżynier</option>
68            <option value="scientist">Naukowiec</option>
69            <option value="doctor">Lekarz</option>
70          </select>
71        </div>
72
73        {/* Radio buttons */}
74        <div className="form-group">
75          <p>Preferowana zmiana:</p>
76          <label>
77            <input
78              type="radio"
79              name="preferredShift"
80              value="morning"
81              checked={formData.preferredShift === 'morning'}
82              onChange={handleChange}
83            />
84            Poranna
85          </label>
86          <label>
87            <input
88              type="radio"
89              name="preferredShift"
90              value="evening"
91              checked={formData.preferredShift === 'evening'}
92              onChange={handleChange}
93            />
94            Wieczorna
95          </label>
96        </div>
97
98        {/* Checkbox */}
99        <div className="form-group">
100          <label>
101            <input
102              type="checkbox"
103              name="isTrainee"
104              checked={formData.isTrainee}
105              onChange={handleChange}
106            />
107            Status praktykanta
108          </label>
109        </div>
110
111        {/* Textarea */}
112        <div className="form-group">
113          <label htmlFor="bio">Biografia:</label>
114          <textarea
115            id="bio"
116            name="bio"
117            value={formData.bio}
118            onChange={handleChange}
119            rows="4"
120          ></textarea>
121        </div>
122
123        {/* Range */}
124        <div className="form-group">
125          <label htmlFor="experience">Lata doświadczenia: {formData.experience}</label>
126          <input
127            type="range"
128            id="experience"
129            name="experience"
130            min="0"
131            max="30"
132            value={formData.experience}
133            onChange={handleChange}
134          />
135        </div>
136
137        <button type="submit">Zarejestruj</button>
138      </form>
139    </div>
140  );
141}

Obsługa błędów i walidacja formularzy

W rzeczywistych aplikacjach, ważne jest, aby walidować dane wprowadzane przez użytkownika:

1function MissionPlanner() {
2  const [formData, setFormData] = useState({
3    missionName: '',
4    launchDate: '',
5    budget: ''
6  });
7
8  const [errors, setErrors] = useState({});
9
10  const validateForm = () => {
11    const newErrors = {};
12
13    // Walidacja nazwy misji
14    if (!formData.missionName.trim()) {
15      newErrors.missionName = 'Nazwa misji jest wymagana';
16    } else if (formData.missionName.length < 3) {
17      newErrors.missionName = 'Nazwa misji musi mieć co najmniej 3 znaki';
18    }
19
20    // Walidacja daty startu
21    if (!formData.launchDate) {
22      newErrors.launchDate = 'Data startu jest wymagana';
23    } else {
24      const selectedDate = new Date(formData.launchDate);
25      const today = new Date();
26
27      if (selectedDate <= today) {
28        newErrors.launchDate = 'Data startu musi być w przyszłości';
29      }
30    }
31
32    // Walidacja budżetu
33    if (!formData.budget) {
34      newErrors.budget = 'Budżet jest wymagany';
35    } else if (isNaN(formData.budget) || Number(formData.budget) <= 0) {
36      newErrors.budget = 'Budżet musi być liczbą dodatnią';
37    }
38
39    setErrors(newErrors);
40    return Object.keys(newErrors).length === 0; // Formularz jest prawidłowy, jeśli nie ma błędów
41  };
42
43  const handleChange = (e) => {
44    const { name, value } = e.target;
45    setFormData({
46      ...formData,
47      [name]: value
48    });
49
50    // Usuwanie błędu dla pola, które jest zmieniane
51    if (errors[name]) {
52      setErrors({
53        ...errors,
54        [name]: undefined
55      });
56    }
57  };
58
59  const handleSubmit = (e) => {
60    e.preventDefault();
61
62    if (validateForm()) {
63      console.log('Dane misji zostały wysłane:', formData);
64      // Logika wysyłania danych...
65    } else {
66      console.log('Formularz zawiera błędy');
67    }
68  };
69
70  return (
71    <div className="mission-planner">
72      <h2>Planowanie misji kosmicznej</h2>
73
74      <form onSubmit={handleSubmit}>
75        <div className="form-group">
76          <label htmlFor="missionName">Nazwa misji:</label>
77          <input
78            type="text"
79            id="missionName"
80            name="missionName"
81            value={formData.missionName}
82            onChange={handleChange}
83            className={errors.missionName ? 'error' : ''}
84          />
85          {errors.missionName && <div className="error-message">{errors.missionName}</div>}
86        </div>
87
88        <div className="form-group">
89          <label htmlFor="launchDate">Data startu:</label>
90          <input
91            type="date"
92            id="launchDate"
93            name="launchDate"
94            value={formData.launchDate}
95            onChange={handleChange}
96            className={errors.launchDate ? 'error' : ''}
97          />
98          {errors.launchDate && <div className="error-message">{errors.launchDate}</div>}
99        </div>
100
101        <div className="form-group">
102          <label htmlFor="budget">Budżet (w milionach $):</label>
103          <input
104            type="number"
105            id="budget"
106            name="budget"
107            value={formData.budget}
108            onChange={handleChange}
109            className={errors.budget ? 'error' : ''}
110          />
111          {errors.budget && <div className="error-message">{errors.budget}</div>}
112        </div>
113
114        <button type="submit">Zaplanuj misję</button>
115      </form>
116    </div>
117  );
118}

Podsumowanie

Obsługa zdarzeń jest kluczowym elementem tworzenia interaktywnych aplikacji w React. Dzięki niej możemy:

  1. Reagować na działania użytkownika, takie jak kliknięcia, ruch myszy, czy wprowadzanie tekstu
  2. Aktualizować stan komponentu na podstawie interakcji
  3. Komunikować się między komponentami poprzez przekazywanie funkcji jako props
  4. Obsługiwać formularze i walidować dane wprowadzane przez użytkownika

Prawidłowa obsługa zdarzeń, w połączeniu z zarządzaniem stanem, pozwala tworzyć dynamiczne, responsywne interfejsy użytkownika, które zapewniają użytkownikom intuicyjne doświadczenie.

Kod do tej lekcji: App.jsx
1import React, { useState } from 'react';
2
3function EventDemo() {
4  const [clickCount, setClickCount] = useState(0);
5  const [inputText, setInputText] = useState('');
6  const [isHovered, setIsHovered] = useState(false);
7  const [selectedOption, setSelectedOption] = useState('Mars');
8
9  const handleButtonClick = () => {
10    setClickCount(clickCount + 1);
11  };
12
13  const handleInputChange = (event) => {
14    setInputText(event.target.value);
15  };
16
17  const handleSubmit = (event) => {
18    event.preventDefault();
19    alert(`Formularz wysłany z tekstem: "${inputText}"`);
20  };
21
22  const handleKeyDown = (event) => {
23    if (event.key === 'Enter') {
24      alert('Naciśnięto Enter!');
25    }
26  };
27
28  return (
29    <div className="event-demo">
30      <h1>Demo obsługi zdarzeń w React</h1>
31
32      <div className="demo-section">
33        <h2>Zdarzenia myszy</h2>
34        <button
35          onClick={handleButtonClick}
36          onMouseEnter={() => setIsHovered(true)}
37          onMouseLeave={() => setIsHovered(false)}
38          className={isHovered ? 'hovered' : ''}
39        >
40          Kliknij mnie! (Kliknięto: {clickCount} razy)
41        </button>
42      </div>
43
44      <div className="demo-section">
45        <h2>Zdarzenia formularza</h2>
46        <form onSubmit={handleSubmit}>
47          <input
48            type="text"
49            value={inputText}
50            onChange={handleInputChange}
51            onKeyDown={handleKeyDown}
52            placeholder="Wpisz tekst i naciśnij Enter lub wyślij formularz"
53          />
54          <select
55            value={selectedOption}
56            onChange={(e) => setSelectedOption(e.target.value)}
57          >
58            <option value="Mars">Mars</option>
59            <option value="Księżyc">Księżyc</option>
60            <option value="Wenus">Wenus</option>
61            <option value="Jowisz">Jowisz</option>
62          </select>
63          <button type="submit">Wyślij formularz</button>
64        </form>
65
66        <div className="output">
67          <p>Aktualny tekst: {inputText}</p>
68          <p>Wybrana opcja: {selectedOption}</p>
69        </div>
70      </div>
71
72      <div className="demo-section">
73        <h2>Zdarzenia dotyku i interakcji</h2>
74        <div
75          className="interactive-area"
76          onTouchStart={() => console.log('Dotyk rozpoczęty')}
77          onTouchEnd={() => console.log('Dotyk zakończony')}
78          onDoubleClick={() => alert('Podwójne kliknięcie!')}
79        >
80          Kliknij podwójnie lub dotknij (sprawdź konsolę)
81        </div>
82      </div>
83    </div>
84  );
85}
86
87function App() {
88  return (
89    <div className="app">
90      <EventDemo />
91    </div>
92  );
93}
94
95export default App;

Widzisz błąd w tej lekcji?

Zadania praktyczne w grze

  • Układanie w poziomie

    Ułóż poprawną składnię aktualizacji stanu z użyciem poprzedniej wartości:

  • Edytor kodu

    Stwórz licznik tlenu: const [oxygen, setOxygen] = useState(21). Wyświetl tekst Poziom O2: {oxygen}% i dodaj dwa przyciski: przycisk + zwiększa poziom o 1 (<button onClick={() => setOxygen(prev => prev + 1)}>+</button>), a przycisk - zmniejsza go o 1. Poziom nie może wyjść poza zakres 0-100.

  • Klikanie w kolejności

    Zbuduj poprawny atrybut obsługi kliknięcia w JSX:

  • Klikanie w kolejności

    Ułóż proces używania useState:

Przydatne artykuły