Kurs JavaScript i React · Moduł 6: React Router
Przekazywanie stanu przez lokalizacje w międzygalaktycznej komunikacji
W tej lekcji9
W kosmicznej podróży, czasami potrzebujemy przesłać ważne dane z jednego sektora stacji kosmicznej do drugiego. W aplikacjach React wykorzystujących React Router podobnie przekazujemy stan (dane) między różnymi stronami za pomocą mechanizmu location state. Zobaczmy, jak wykorzystać ten mechanizm do usprawnienia naszej międzygalaktycznej komunikacji.
Czym jest Location State?
Location state to mechanizm w React Router, który pozwala na przekazywanie danych podczas nawigacji między różnymi ścieżkami bez konieczności używania parametrów URL, cookies, czy globalnego stanu aplikacji. Dane te nie są widoczne w URL, ale są dostępne na stronie docelowej.
Przekazywanie stanu przez Link
Najprostszym sposobem przekazania danych przez location state jest użycie komponentu Link z dodatkowym parametrem state:
1import { Link } from 'react-router-dom';
2
3function PlanetList() {
4 const planets = [
5 { id: 1, name: 'Mars', atmosphere: 'Cienka, głównie CO2', temperature: 'Od -125°C do 20°C' },
6 { id: 2, name: 'Wenus', atmosphere: 'Gęsta, głównie CO2', temperature: 'Średnio 462°C' },
7 { id: 3, name: 'Tytan', atmosphere: 'Gęsta, azot i metan', temperature: 'Średnio -179°C' }
8 ];
9
10 return (
11 <div className="planet-explorer">
12 <h1>Eksploracja Planet</h1>
13 <ul className="planet-list">
14 {planets.map(planet => (
15 <li key={planet.id}>
16 <Link
17 to={`/planet/${planet.id}`}
18 state={{ planetData: planet }}
19 >
20 Eksploruj {planet.name}
21 </Link>
22 </li>
23 ))}
24 </ul>
25 </div>
26 );
27}W powyższym przykładzie, kliknięcie w link przeniesie użytkownika do strony /planet/1 (dla Marsa) i jednocześnie przekaże obiekt planetData zawierający wszystkie informacje o planecie.
Odbieranie stanu w komponencie docelowym
Aby odczytać przekazany stan w komponencie docelowym, używamy hooka useLocation:
1import { useParams, useLocation } from 'react-router-dom';
2
3function PlanetDetails() {
4 // Pobieramy parametr z URL
5 const { id } = useParams();
6
7 // Pobieramy obiekt location, który zawiera przekazany stan
8 const location = useLocation();
9
10 // Wyciągamy dane planety z stanu lokalizacji, z fallbackiem na pusty obiekt
11 const { planetData } = location.state || {};
12
13 // Jeśli nie otrzymaliśmy danych planety, możemy pokazać komunikat o błędzie
14 // lub próbować pobrać dane na podstawie ID
15 if (!planetData) {
16 return (
17 <div className="error-message">
18 <h2>Brak danych planety</h2>
19 <p>Nie udało się wczytać danych dla planety o ID: {id}</p>
20 <Link to="/">Wróć do listy planet</Link>
21 </div>
22 );
23 }
24
25 return (
26 <div className="planet-details">
27 <h1>Szczegóły planety: {planetData.name}</h1>
28
29 <div className="planet-info">
30 <p><strong>Atmosfera:</strong> {planetData.atmosphere}</p>
31 <p><strong>Temperatura:</strong> {planetData.temperature}</p>
32 </div>
33
34 <Link to="/">Wróć do listy planet</Link>
35 </div>
36 );
37}Programowe przekazywanie stanu
Możemy również przekazywać stan programowo za pomocą hooka useNavigate:
1import { useNavigate } from 'react-router-dom';
2
3function MissionControl() {
4 const navigate = useNavigate();
5
6 const startMission = (mission) => {
7 // Wykonujemy jakieś operacje przed przejściem do następnej strony
8 const missionData = {
9 ...mission,
10 startDate: new Date().toISOString(),
11 commander: 'Kapitan Alex Chen',
12 status: 'W trakcie'
13 };
14
15 // Przekierowujemy do strony misji z danymi
16 navigate(`/mission/${mission.id}`, {
17 state: { missionData }
18 });
19 };
20
21 return (
22 <div className="mission-control">
23 <h1>Centrum Dowodzenia Misjami</h1>
24
25 <div className="mission-list">
26 <div className="mission-card">
27 <h3>Misja: Eksploracja Marsa</h3>
28 <p>Cel: Badanie próbek gleby</p>
29 <button onClick={() => startMission({
30 id: 'mars-exploration-1',
31 name: 'Eksploracja Marsa',
32 objective: 'Badanie próbek gleby'
33 })}>
34 Rozpocznij misję
35 </button>
36 </div>
37
38 <div className="mission-card">
39 <h3>Misja: Stacja Satelitarna</h3>
40 <p>Cel: Naprawa układów komunikacyjnych</p>
41 <button onClick={() => startMission({
42 id: 'satellite-repair-1',
43 name: 'Stacja Satelitarna',
44 objective: 'Naprawa układów komunikacyjnych'
45 })}>
46 Rozpocznij misję
47 </button>
48 </div>
49 </div>
50 </div>
51 );
52}Zaawansowane przypadki użycia
1. Przekazywanie informacji o poprzedniej lokalizacji
Możemy używać location state do zapamiętania strony, z której użytkownik przybył:
1function SecureArea() {
2 const navigate = useNavigate();
3 const { user } = useAuth();
4
5 useEffect(() => {
6 if (!user) {
7 // Przekierowanie do logowania z informacją o bieżącej ścieżce
8 navigate('/login', {
9 state: { from: location.pathname },
10 replace: true
11 });
12 }
13 }, [user, navigate, location]);
14
15 return (
16 <div className="secure-area">
17 <h1>Bezpieczna Sekcja Statku Kosmicznego</h1>
18 {/* Zawartość bezpiecznej sekcji */}
19 </div>
20 );
21}
22
23// W komponencie logowania
24function Login() {
25 const location = useLocation();
26 const navigate = useNavigate();
27
28 // Pobieramy oryginalną ścieżkę, na którą użytkownik próbował się dostać
29 const from = location.state?.from || '/dashboard';
30
31 const handleLogin = async (credentials) => {
32 const success = await performLogin(credentials);
33
34 if (success) {
35 // Po zalogowaniu wracamy na oryginalną stronę
36 navigate(from, { replace: true });
37 }
38 };
39
40 // Reszta komponentu logowania...
41}2. Przekazywanie danych formularza w wieloetapowym procesie
Innym zaawansowanym przypadkiem jest zachowanie danych formularza podczas nawigacji w wieloetapowym procesie:
1function SpaceshipConfigStep1() {
2 const [config, setConfig] = useState({
3 name: '',
4 type: 'exploration',
5 crew: 4
6 });
7
8 const navigate = useNavigate();
9
10 const handleNext = () => {
11 // Walidacja formularza...
12
13 // Przejście do następnego kroku z danymi
14 navigate('/config/step2', {
15 state: { config }
16 });
17 };
18
19 return (
20 <div className="config-form">
21 <h2>Konfiguracja Statku Kosmicznego - Krok 1</h2>
22 <form onSubmit={e => { e.preventDefault(); handleNext(); }}>
23 <div className="form-field">
24 <label htmlFor="name">Nazwa statku:</label>
25 <input
26 id="name"
27 value={config.name}
28 onChange={e => setConfig({...config, name: e.target.value})}
29 required
30 />
31 </div>
32
33 <div className="form-field">
34 <label htmlFor="type">Typ misji:</label>
35 <select
36 id="type"
37 value={config.type}
38 onChange={e => setConfig({...config, type: e.target.value})}
39 >
40 <option value="exploration">Eksploracja</option>
41 <option value="science">Badania naukowe</option>
42 <option value="mining">Wydobycie zasobów</option>
43 </select>
44 </div>
45
46 <div className="form-field">
47 <label htmlFor="crew">Liczba załogi:</label>
48 <input
49 id="crew"
50 type="number"
51 min="1"
52 max="10"
53 value={config.crew}
54 onChange={e => setConfig({...config, crew: parseInt(e.target.value)})}
55 required
56 />
57 </div>
58
59 <button type="submit">Dalej</button>
60 </form>
61 </div>
62 );
63}
64
65function SpaceshipConfigStep2() {
66 const location = useLocation();
67 const navigate = useNavigate();
68
69 // Pobieramy konfigurację z poprzedniego kroku
70 const { config: prevConfig } = location.state || { config: null };
71
72 // Jeśli nie mamy konfiguracji, przekierowujemy z powrotem
73 useEffect(() => {
74 if (!prevConfig) {
75 navigate('/config/step1', { replace: true });
76 }
77 }, [prevConfig, navigate]);
78
79 // Inicjalizujemy stan z danymi z poprzedniego kroku
80 const [config, setConfig] = useState({
81 ...prevConfig,
82 engines: 'ion',
83 fuelCapacity: 5000,
84 lifeSupport: 'advanced'
85 });
86
87 const handleFinish = () => {
88 // Kompletna konfiguracja z obu kroków
89 const finalConfig = {
90 ...config
91 };
92
93 // Finalizujemy konfigurację
94 navigate('/config/summary', {
95 state: { finalConfig }
96 });
97 };
98
99 if (!prevConfig) return null; // Nie renderuj nic, dopóki przekierowanie się nie wykona
100
101 return (
102 <div className="config-form">
103 <h2>Konfiguracja Statku Kosmicznego - Krok 2</h2>
104 <form onSubmit={e => { e.preventDefault(); handleFinish(); }}>
105 {/* Drugi zestaw pól formularza */}
106
107 <div className="form-actions">
108 <button
109 type="button"
110 onClick={() => navigate(-1)}
111 >
112 Wstecz
113 </button>
114 <button type="submit">Zakończ</button>
115 </div>
116 </form>
117 </div>
118 );
119}Funkcja replace i historia przeglądarki
W przykładach używaliśmy parametru replace: true. Jest to ważna opcja, która wpływa na zachowanie historii przeglądarki:
- Gdy
replace: false(domyślne) - dodaje nową stronę do historii. Użytkownik może wrócić do poprzedniej strony przyciskiem "Wstecz". - Gdy
replace: true- zastępuje bieżącą stronę w historii. Gdy użytkownik naciśnie przycisk "Wstecz", wróci do strony sprzed ostatniego przekierowania.
1// Dodaje nową stronę do historii przeglądarki
2navigate('/dashboard', { state: { data: 'example' } });
3
4// Zastępuje bieżącą stronę w historii
5navigate('/dashboard', {
6 state: { data: 'example' },
7 replace: true
8});Opcja replace jest szczególnie przydatna w procesach logowania, gdzie po przekierowaniu użytkownika do strony logowania i z powrotem, nie chcemy, aby mógł wrócić do strony logowania za pomocą przycisku "Wstecz".
Ograniczenia i alternatywy
Location state ma pewne ograniczenia:
- Nie jest trwały - dane są tracone przy odświeżeniu strony
- Limitowana wielkość - nie powinniśmy przekazywać bardzo dużych obiektów
- Nie jest widoczny w URL - co może być zaletą (prywatność) lub wadą (nie można zapamiętać/udostępnić dokładnego stanu)
Alternatywne metody przekazywania danych między stronami:
Parametry URL: Dane są widoczne w adresie URL
1// Przekazywanie przez URL 2navigate(`/planet/mars?atmosphere=thin&temperature=-60`); 3 4// Odczytywanie 5const { search } = useLocation(); 6const queryParams = new URLSearchParams(search); 7const atmosphere = queryParams.get('atmosphere'); // 'thin'Globalny stan aplikacji: Zarządzanie stanem za pomocą Context API, Redux, Zustand, itp.
1// Zapisanie danych 2dispatch(setCurrentPlanet({ name: 'Mars', atmosphere: 'thin' })); 3navigate('/planet-details'); 4 5// Odczytanie danych 6const currentPlanet = useSelector(state => state.planets.current);Lokalne przechowywanie: sessionStorage, localStorage
1// Zapisanie danych 2sessionStorage.setItem('planetData', JSON.stringify({ name: 'Mars' })); 3navigate('/planet-details'); 4 5// Odczytanie danych 6const planetData = JSON.parse(sessionStorage.getItem('planetData') || '{}');
Najlepsze praktyki
- Minimalny zakres danych: Przekazuj tylko niezbędne dane
- Walidacja danych: Zawsze sprawdzaj, czy dane istnieją przed ich użyciem
- Zapewniaj fallback: Miej plan B, gdy dane nie są dostępne
- Unikaj wrażliwych danych: Nie przekazuj poufnych informacji przez location state
- Używaj typesafe: Jeśli korzystasz z TypeScript, określaj typy dla danych w location state
Podsumowanie
Przekazywanie stanu przez lokalizacje w React Router jest jak międzygalaktyczna komunikacja - możemy wysyłać istotne dane między różnymi "stacjami" (stronami) naszej aplikacji bez konieczności zapamiętywania ich globalnie.
Ta technika jest szczególnie przydatna w przypadkach takich jak:
- Przekazywanie szczegółów obiektu na stronę szczegółową
- Zapamiętywanie oryginalnej lokalizacji przy przekierowaniach uwierzytelniania
- Zachowywanie stanu formularza w procesach wieloetapowych
- Przekazywanie wiadomości i komunikatów między stronami (np. "Operacja zakończona pomyślnie")
Wybierając między location state a innymi metodami przekazywania danych, zawsze rozważaj trwałość, widoczność i wielkość danych, aby wybrać najlepsze podejście dla konkretnego przypadku użycia.
Kod do tej lekcji: App.jsx
1import React from 'react';
2import { BrowserRouter, Routes, Route, Link, Navigate, useNavigate } from 'react-router-dom';
3
4// Strona 404
5function NotFoundPage() {
6 const navigate = useNavigate();
7
8 return (
9 <div className="page not-found">
10 <div className="error-code">404</div>
11 <h1>Zagubiony w kosmosie</h1>
12 <p>Ta strona nie istnieje w naszym wszechświecie.</p>
13 <div className="actions">
14 <button onClick={() => navigate('/')}>Wróć do domu</button>
15 <button onClick={() => navigate(-1)}>← Cofnij</button>
16 </div>
17 </div>
18 );
19}
20
21// Strona główna
22function HomePage() {
23 return (
24 <div className="page">
25 <h1>Strona główna</h1>
26 <p>Witaj w centrum kontroli!</p>
27 </div>
28 );
29}
30
31// Strona profilu (przekierowanie ze starego URL)
32function ProfilePage() {
33 return (
34 <div className="page">
35 <h1>Profil użytkownika</h1>
36 <p>Twoje dane profilowe.</p>
37 </div>
38 );
39}
40
41// Strona ustawień
42function SettingsPage() {
43 return (
44 <div className="page">
45 <h1>Ustawienia</h1>
46 <p>Konfiguracja systemu.</p>
47 </div>
48 );
49}
50
51// Stara strona (przekierowanie)
52function OldPage() {
53 return <Navigate to="/new-page" replace />;
54}
55
56function NewPage() {
57 return (
58 <div className="page">
59 <h1>Nowa strona</h1>
60 <p>Zostałeś przekierowany ze starej strony!</p>
61 </div>
62 );
63}
64
65function Navigation() {
66 return (
67 <nav>
68 <Link to="/">Home</Link>
69 <Link to="/profile">Profil</Link>
70 <Link to="/settings">Ustawienia</Link>
71 <Link to="/old-page">Stara strona (redirect)</Link>
72 <Link to="/non-existent">Nieistniejąca</Link>
73 </nav>
74 );
75}
76
77function App() {
78 return (
79 <BrowserRouter>
80 <div className="app">
81 <Navigation />
82 <Routes>
83 <Route path="/" element={<HomePage />} />
84 <Route path="/profile" element={<ProfilePage />} />
85 <Route path="/settings" element={<SettingsPage />} />
86
87 {/* Przekierowanie ze starego URL na nowy */}
88 <Route path="/old-page" element={<Navigate to="/new-page" replace />} />
89 <Route path="/new-page" element={<NewPage />} />
90
91 {/* Przekierowanie z /user na /profile */}
92 <Route path="/user" element={<Navigate to="/profile" />} />
93 <Route path="/user/:id" element={<Navigate to="/profile" />} />
94
95 {/* Catch-all route - 404 */}
96 <Route path="*" element={<NotFoundPage />} />
97 </Routes>
98 </div>
99 </BrowserRouter>
100 );
101}
102
103export 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. W jaki sposób można przekazać dane do następnej strony za pomocą komponentu Link?
2. Jakiego hooka użyjesz, aby odczytać dane przekazane przez location state?
Zadania praktyczne w grze
- Klikanie w kolejności
Ułóż składnię programowego przekazywania stanu za pomocą navigate: