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ą.
W React, obsługa zdarzeń działa podobnie jak w standardowym HTML, ale z kilkoma istotnymi różnicami:
Nazwy zdarzeń używają konwencji camelCase zamiast małych liter:
onclick, onmouseoveronClick, onMouseOverW JSX przekazujemy funkcję jako handler zdarzenia, a nie string:
<button onclick="handleClick()">Kliknij</button><button onClick={handleClick}>Kliknij</button>Aby zapobiec domyślnemu zachowaniu, musimy jawnie wywołać
preventDefault:<a href="#" onclick="return false">Kliknij</a><a href="#" onClick={(e) => e.preventDefault()}>Kliknij</a>Zdarzenia to sposób, w jaki komponenty React reagują na działania użytkownika. Przyjrzyjmy się podstawowym typom zdarzeń i jak je obsługiwać.
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}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}Czasami potrzebujemy przekazać dodatkowe argumenty do handlera zdarzenia. Możemy to zrobić na dwa sposoby:
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}bind1function 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.
React obsługuje szeroki zakres zdarzeń, które możemy podzielić na kilka kategorii:
onClick - kliknięcieonDoubleClick - podwójne kliknięcieonMouseEnter - najechanie kursorem na elementonMouseLeave - wyjście kursorem poza elementonMouseMove - ruch kursora nad elementemonChange - zmiana wartości elementu formularzaonSubmit - wysłanie formularzaonFocus - element otrzymuje focusonBlur - element traci focusonKeyDown - wciśnięcie klawiszaonKeyPress - wciśnięcie klawisza (znak)onKeyUp - zwolnienie klawiszaonTouchStart - rozpoczęcie dotykuonTouchMove - ruch podczas dotykuonTouchEnd - zakończenie dotyku1function 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}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}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}Formularze są kluczowym elementem interakcji z użytkownikiem w aplikacjach webowych. React oferuje dwa podejścia do obsługi formularzy: kontrolowane i niekontrolowane komponenty.
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:
onChange aktualizuje stan, co z kolei aktualizuje wartość polaonSubmit jest używane do obsługi przesyłania formularzaRóż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}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}Obsługa zdarzeń jest kluczowym elementem tworzenia interaktywnych aplikacji w React. Dzięki niej możemy:
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.