Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Obsługa zdarzeń

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}

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
    - wciśnięcie klawisza (znak)
  • 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 z stanu

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

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  // Rejestracja zdarzenia na poziomie dokumentu
24  useEffect(() => {
25    document.addEventListener('keydown', handleKeyDown);
26    
27    // Ważne jest, aby wyczyścić listenery przy odmontowaniu komponentu
28    return () => {
29      document.removeEventListener('keydown', handleKeyDown);
30    };
31  }, []);
32  
33  return (
34    <div className="spaceship-simulator">
35      <h2>Symulator statku kosmicznego</h2>
36      <p>Użyj strzałek do sterowania statkiem</p>
37      
38      <div className="universe" style={{ position: 'relative', width: '400px', height: '400px', background: 'black' }}>
39        <div 
40          className="spaceship" 
41          style={{
42            position: 'absolute',
43            left: `${position.x}%`,
44            top: `${position.y}%`,
45            transform: 'translate(-50%, -50%)',
46            width: '20px',
47            height: '20px',
48            background: 'red'
49          }}
50        ></div>
51      </div>
52      
53      <div className="coordinates">
54        <p>Pozycja X: {position.x}</p>
55        <p>Pozycja Y: {position.y}</p>
56      </div>
57    </div>
58  );
59}

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  
5  const startCountdown = () => {
6    setIsLaunching(true);
7    
8    const timer = setInterval(() => {
9      setCountdown(prev => {
10        if (prev <= 1) {
11          clearInterval(timer);
12          onLaunch(); // Wywołanie funkcji z props po zakończeniu odliczania
13          return 0;
14        }
15        return prev - 1;
16      });
17    }, 1000);
18  };
19  
20  const abortLaunch = () => {
21    setIsLaunching(false);
22    setCountdown(10);
23    onAbort(); // Wywołanie funkcji z props przy przerwaniu
24  };
25  
26  return (
27    <div className="mission-control">
28      <h2>Centrum kontroli misji</h2>
29      <div className="countdown">
30        {isLaunching 
31          ? `Start za: ${countdown}` 
32          : 'Gotowy do startu'}
33      </div>
34      
35      <div className="control-buttons">
36        {!isLaunching ? (
37          <button onClick={startCountdown}>Rozpocznij odliczanie</button>
38        ) : (
39          <button onClick={abortLaunch} className="abort">Przerwij start</button>
40        )}
41      </div>
42    </div>
43  );
44}
45
46// Użycie komponentu
47function SpaceProgram() {
48  const handleLaunch = () => {
49    console.log('Rakieta wystartowała!');
50    // Logika po starcie...
51  };
52  
53  const handleAbort = () => {
54    console.log('Start przerwany!');
55    // Logika po przerwaniu...
56  };
57  
58  return (
59    <div className="space-program">
60      <h1>Program Kosmiczny</h1>
61      <MissionControl 
62        onLaunch={handleLaunch}
63        onAbort={handleAbort}
64      />
65    </div>
66  );
67}

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.

Przejdź do CodeWorlds