Kurs JavaScript i React · Moduł 6: React Router
Nawigacja programistyczna w kosmicznych podróżach
W tej lekcji9
W centrum dowodzenia statkiem kosmicznym potrzebujemy czasem nawigować między różnymi lokalizacjami nie tylko przy pomocy przycisków i linków, ale również programowo - na podstawie określonych warunków, zdarzeń lub logiki aplikacji. React Router dostarcza zaawansowane narzędzia do takiej programistycznej nawigacji, które pozwalają sterować trasą użytkownika z poziomu kodu.
Wprowadzenie do nawigacji programistycznej
Nawigacja programistyczna to technika zmiany adresu URL i renderowanego widoku za pomocą kodu JavaScript, bez bezpośredniej interakcji użytkownika z linkami czy przyciskami. Jest to niezbędny element wielu scenariuszy w aplikacjach:
- Przekierowanie po pomyślnym zalogowaniu
- Nawigacja po zakończeniu wieloetapowego formularza
- Powrót po zapisaniu danych
- Przekierowanie po upływie czasu sesji
- Nawigacja na podstawie odpowiedzi z API
W React Router głównym narzędziem do nawigacji programistycznej jest hook useNavigate.
Hook useNavigate
Hook useNavigate wprowadzony w React Router v6 dostarcza funkcję, która pozwala programowo nawigować do innych ścieżek w aplikacji:
1import { useNavigate } from 'react-router-dom';
2
3function LaunchControlPanel() {
4 const navigate = useNavigate();
5
6 const handleMissionStart = () => {
7 // Logika rozpoczęcia misji...
8 console.log('Misja rozpoczęta!');
9
10 // Po rozpoczęciu misji, przekieruj do panelu monitorowania
11 navigate('/mission/monitoring');
12 };
13
14 return (
15 <div className="launch-control">
16 <h2>Panel kontroli startu</h2>
17 <button
18 className="launch-button"
19 onClick={handleMissionStart}
20 >
21 Rozpocznij misję
22 </button>
23 </div>
24 );
25}Podstawowe użycie navigate
Funkcja navigate może przyjmować różne argumenty:
- Ścieżkę docelową jako string:
1// Nawigacja do konkretnej ścieżki
2navigate('/planets/mars');
3
4// Nawigacja względna (o jeden poziom w górę)
5navigate('..');
6
7// Nawigacja względna (do podścieżki)
8navigate('systems');- Liczbę (delta) dla nawigacji w historii:
1// Cofnij się o jedną stronę (jak przycisk "wstecz" w przeglądarce)
2navigate(-1);
3
4// Idź do przodu o jedną stronę (jak przycisk "do przodu" w przeglądarce)
5navigate(1);
6
7// Cofnij się o dwie strony
8navigate(-2);- Obiekt z opcjami:
1// Nawigacja z opcjami
2navigate('/planets/mars', {
3 replace: true, // Zastępuje bieżący wpis w historii zamiast dodawać nowy
4 state: { prevLocation: '/dashboard' } // Dane stanu do przekazania
5});
6
7// Nawigacja z parametrami wyszukiwania
8navigate({
9 pathname: '/planets',
10 search: '?type=rocky&habitable=true'
11});Opcja replace
Domyślnie, navigate dodaje nowy wpis do historii przeglądarki. Oznacza to, że użytkownik może wrócić do poprzedniej strony za pomocą przycisku "wstecz". Czasami jednak chcemy zastąpić bieżący wpis, aby użytkownik nie mógł wrócić (np. po wylogowaniu):
1function LogoutButton() {
2 const navigate = useNavigate();
3
4 const handleLogout = () => {
5 // Wyloguj użytkownika
6 logout();
7
8 // Przekieruj do strony logowania, zastępując bieżący wpis w historii
9 navigate('/login', { replace: true });
10 };
11
12 return <button onClick={handleLogout}>Wyloguj</button>;
13}Przekazywanie stanu przez nawigację
Możemy przekazać dane stanu do nowej lokalizacji za pomocą opcji state. Te dane nie są widoczne w URL, ale są dostępne w komponencie docelowym:
1// Komponent źródłowy
2function PlanetsList() {
3 const navigate = useNavigate();
4
5 const viewPlanetDetails = (planet) => {
6 navigate(`/planets/${planet.id}`, {
7 state: {
8 planetData: planet,
9 viewedFrom: 'planetsList',
10 timestamp: Date.now()
11 }
12 });
13 };
14
15 return (
16 <div>
17 {planets.map(planet => (
18 <div key={planet.id} onClick={() => viewPlanetDetails(planet)}>
19 {planet.name}
20 </div>
21 ))}
22 </div>
23 );
24}
25
26// Komponent docelowy
27function PlanetDetails() {
28 const { state } = useLocation();
29 const { planetId } = useParams();
30
31 useEffect(() => {
32 if (state?.viewedFrom) {
33 console.log(`Przekierowano z: ${state.viewedFrom}`);
34 console.log(`Dane planety z nawigacji: ${state.planetData}`);
35 }
36 }, [state]);
37
38 return (
39 <div>
40 <h1>Szczegóły planety: {planetId}</h1>
41 {/* Reszta komponentu */}
42 </div>
43 );
44}Budowanie zaawansowanych mechanizmów nawigacji
Wykorzystując hook useNavigate, możemy budować zaawansowane mechanizmy nawigacji, które obsługują złożone przepływy użytkownika:
1. Warunkowe przekierowania
1function ResourceLoader({ resourceId, resourceType }) {
2 const navigate = useNavigate();
3 const [resource, setResource] = useState(null);
4 const [error, setError] = useState(null);
5
6 useEffect(() => {
7 fetchResource(resourceId, resourceType)
8 .then(data => {
9 setResource(data);
10
11 // Warunkowo przekieruj na podstawie typu zasobu
12 if (resourceType === 'mission' && data.status === 'in-progress') {
13 navigate(`/mission-control/${resourceId}`);
14 } else if (resourceType === 'planet' && data.isRestricted) {
15 navigate('/access-denied', {
16 replace: true,
17 state: { reason: 'restricted-planet' }
18 });
19 }
20 })
21 .catch(err => {
22 setError(err);
23
24 // Przekieruj do strony błędu w przypadku problemu
25 navigate('/error', {
26 state: {
27 error: err.message,
28 resourceId,
29 resourceType
30 }
31 });
32 });
33 }, [resourceId, resourceType, navigate]);
34
35 if (error) return <p>Ładowanie...</p>;
36 if (!resource) return <p>Ładowanie...</p>;
37
38 return <ResourceViewer resource={resource} />;
39}2. Sterowany przepływ wieloetapowy
1function MissionPlanner() {
2 const navigate = useNavigate();
3 const [currentStep, setCurrentStep] = useState(1);
4 const [missionData, setMissionData] = useState({});
5
6 const handleStepComplete = (stepData) => {
7 // Uaktualnij dane misji o dane z bieżącego kroku
8 const updatedData = { ...missionData, ...stepData };
9 setMissionData(updatedData);
10
11 if (currentStep < 3) {
12 // Przejdź do następnego kroku
13 setCurrentStep(currentStep + 1);
14 } else {
15 // Wszystkie kroki zakończone, zapisz misję i przekieruj
16 saveMission(updatedData)
17 .then(mission => {
18 navigate(`/missions/${mission.id}/overview`, {
19 state: { missionData: mission, isNewMission: true }
20 });
21 })
22 .catch(error => {
23 navigate('/error', { state: { error } });
24 });
25 }
26 };
27
28 const handleCancel = () => {
29 // Zapytaj użytkownika, czy na pewno chce anulować
30 if (window.confirm('Czy na pewno chcesz anulować planowanie misji?')) {
31 navigate('/missions');
32 }
33 };
34
35 return (
36 <div className="mission-planner">
37 <h1>Planowanie nowej misji</h1>
38 <ProgressBar currentStep={currentStep} totalSteps={3} />
39
40 {currentStep === 1 && (
41 <MissionBasicInfoStep
42 onComplete={handleStepComplete}
43 initialData={missionData}
44 />
45 )}
46
47 {currentStep === 2 && (
48 <MissionCrewSelectionStep
49 onComplete={handleStepComplete}
50 initialData={missionData}
51 />
52 )}
53
54 {currentStep === 3 && (
55 <MissionConfirmationStep
56 onComplete={handleStepComplete}
57 initialData={missionData}
58 />
59 )}
60
61 <div className="actions">
62 {currentStep > 1 && (
63 <button onClick={() => setCurrentStep(currentStep - 1)}>
64 Wstecz
65 </button>
66 )}
67
68 <button onClick={handleCancel}>Anuluj</button>
69 </div>
70 </div>
71 );
72}3. Przekierowania oparte na czasie
1function SessionTimer() {
2 const navigate = useNavigate();
3 const [timeLeft, setTimeLeft] = useState(300); // 5 minut
4
5 useEffect(() => {
6 if (timeLeft <= 0) {
7 // Sesja wygasła, przekieruj do strony logowania
8 alert('Twoja sesja wygasła. Zaloguj się ponownie.');
9 navigate('/login', {
10 replace: true,
11 state: { sessionExpired: true }
12 });
13 return;
14 }
15
16 // Uaktualnij timer co sekundę
17 const timer = setTimeout(() => {
18 setTimeLeft(timeLeft - 1);
19 }, 1000);
20
21 return () => clearTimeout(timer);
22 }, [timeLeft, navigate]);
23
24 const formatTime = () => {
25 const minutes = Math.floor(timeLeft / 60);
26 const seconds = timeLeft % 60;
27 return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
28 };
29
30 return (
31 <div className="session-timer">
32 <span>Pozostały czas sesji: {formatTime()}</span>
33 </div>
34 );
35}Nawigacja na podstawie odpowiedzi API
Bardzo powszechnym przypadkiem użycia nawigacji programistycznej jest przekierowanie na podstawie odpowiedzi z API. Oto kilka typowych scenariuszy:
1. Przekierowanie po pomyślnym przesłaniu formularza
1function NewPlanetForm() {
2 const navigate = useNavigate();
3 const [isSubmitting, setIsSubmitting] = useState(false);
4 const [error, setError] = useState(null);
5
6 const handleSubmit = async (event) => {
7 event.preventDefault();
8 setIsSubmitting(true);
9 setError(null);
10
11 try {
12 const formData = new FormData(event.target);
13 const planetData = {
14 name: formData.get('name'),
15 type: formData.get('type'),
16 diameter: Number(formData.get('diameter')),
17 hasWater: formData.get('hasWater') === 'on'
18 };
19
20 // Wyślij dane do API
21 const response = await fetch('/api/planets', {
22 method: 'POST',
23 headers: {
24 'Content-Type': 'application/json'
25 },
26 body: JSON.stringify(planetData)
27 });
28
29 if (!response.ok) {
30 throw new Error('Błąd podczas dodawania planety');
31 }
32
33 const newPlanet = await response.json();
34
35 // Przekieruj do strony szczegółów nowej planety
36 navigate(`/planets/${newPlanet.id}`, {
37 state: {
38 message: 'Planeta została pomyślnie dodana!',
39 planetData: newPlanet
40 }
41 });
42 } catch (err) {
43 setError(err.message);
44 setIsSubmitting(false);
45 }
46 };
47
48 return (
49 <form onSubmit={handleSubmit}>
50 {/* Pola formularza */}
51 {error && <div className="error">{error}</div>}
52 <button type="submit" disabled={isSubmitting}>
53 {isSubmitting ? 'Dodawanie...' : 'Dodaj planetę'}
54 </button>
55 </form>
56 );
57}2. Przekierowanie po pomyślnym logowaniu
1function LoginForm() {
2 const navigate = useNavigate();
3 const location = useLocation();
4
5 // Sprawdź, czy użytkownik został przekierowany z innej strony
6 const from = location.state?.from?.pathname || '/dashboard';
7
8 const handleSubmit = async (event) => {
9 event.preventDefault();
10 const formData = new FormData(event.target);
11
12 try {
13 // Wywołaj API logowania
14 const user = await loginUser(
15 formData.get('username'),
16 formData.get('password')
17 );
18
19 // Zapisz dane użytkownika w stanie aplikacji
20 setUserInContext(user);
21
22 // Przekieruj do pierwotnej lokalizacji lub dashboardu
23 navigate(from, { replace: true });
24 } catch (error) {
25 // Obsłuż błąd logowania
26 //
27 }
28 };
29
30 return (
31 <form onSubmit={handleSubmit}>
32 {/* Pola formularza logowania */}
33 </form>
34 );
35}Strażnicy tras (Route Guards)
Wykorzystując useNavigate, możemy tworzyć strażników tras, którzy automatycznie przekierowują użytkownika, jeśli nie spełnia określonych warunków. Ta technika jest często używana do zabezpieczania tras wymagających uwierzytelnienia:
1function RequireAuth({ children }) {
2 const navigate = useNavigate();
3 const location = useLocation();
4 const { user } = useAuth();
5
6 useEffect(() => {
7 if (!user) {
8 // Przekieruj do strony logowania, zachowując informację,
9 // skąd użytkownik przyszedł, aby móc go tam wrócić po zalogowaniu
10 navigate('/login', {
11 replace: true,
12 state: { from: location }
13 });
14 }
15 }, [user, navigate, location]);
16
17 // Jeśli użytkownik jest zalogowany, renderuj dzieci (chronioną trasę)
18 return user ? children : null;
19}
20
21// Użycie w definicji tras
22<Route
23 path="/mission-control"
24 element={
25 <RequireAuth>
26 <MissionControlPanel />
27 </RequireAuth>
28 }
29/>Nawigacja z parametrami wyszukiwania
Możemy użyć navigate do nawigowania z parametrami wyszukiwania, co jest przydatne dla filtrowania, sortowania i paginacji:
1function GalaxiesExplorer() {
2 const navigate = useNavigate();
3 const [searchParams] = useSearchParams();
4
5 // Odczytaj aktualne filtry z URL
6 const currentType = searchParams.get('type') || 'all';
7 const currentSort = searchParams.get('sort') || 'name';
8
9 const applyFilters = (filters) => {
10 // Stwórz nowy obiekt URLSearchParams
11 const params = new URLSearchParams();
12
13 // Dodaj filtry do parametrów
14 if (filters.type && filters.type !== 'all') {
15 params.set('type', filters.type);
16 }
17
18 if (filters.sort) {
19 params.set('sort', filters.sort);
20 }
21
22 // Nawiguj do tej samej ścieżki, ale z nowymi parametrami
23 navigate({
24 pathname: '/galaxies',
25 search: params.toString() ? `?${params.toString()}` : ''
26 });
27 };
28
29 return (
30 <div className="galaxies-explorer">
31 <div className="filters">
32 <label>
33 Typ galaktyki:
34 <select
35 value={currentType}
36 onChange={(e) => applyFilters({
37 type: e.target.value,
38 sort: currentSort
39 })}
40 >
41 <option value="all">Wszystkie</option>
42 <option value="spiral">Spiralne</option>
43 <option value="elliptical">Eliptyczne</option>
44 <option value="irregular">Nieregularne</option>
45 </select>
46 </label>
47
48 <label>
49 Sortuj wg:
50 <select
51 value={currentSort}
52 onChange={(e) => applyFilters({
53 type: currentType,
54 sort: e.target.value
55 })}
56 >
57 <option value="name">Nazwa</option>
58 <option value="size">Rozmiar</option>
59 <option value="distance">Odległość</option>
60 </select>
61 </label>
62 </div>
63
64 {/* Lista galaktyk */}
65 </div>
66 );
67}Użycie navigate z generatorem URL
Możemy połączyć navigate z funkcją createSearchParams dla łatwiejszego tworzenia parametrów wyszukiwania:
1import { useNavigate, createSearchParams } from 'react-router-dom';
2
3function PlanetFilters() {
4 const navigate = useNavigate();
5
6 const applyFilters = (filters) => {
7 // Użyj createSearchParams do wygenerowania string'a z parametrami
8 const params = createSearchParams({
9 type: filters.type,
10 hasWater: filters.hasWater ? 'true' : undefined,
11 minDiameter: filters.minDiameter,
12 sortBy: filters.sortBy
13 });
14
15 navigate({
16 pathname: '/planets',
17 search: `?${params}`
18 });
19 };
20
21 return (
22 <form onSubmit={(e) => {
23 e.preventDefault();
24 applyFilters({
25 type: e.target.type.value,
26 hasWater: e.target.hasWater.checked,
27 minDiameter: e.target.minDiameter.value,
28 sortBy: e.target.sortBy.value
29 });
30 }}>
31 {/* Pola formularza filtrów */}
32 <button type="submit">Zastosuj filtry</button>
33 </form>
34 );
35}Problemy i pułapki związane z nawigacją programistyczną
1. Przekierowania podczas renderowania
Unikaj bezpośredniego używania navigate podczas renderowania komponentu - powoduje to problemy z wielokrotnym renderowaniem. Zamiast tego, używaj navigate w efektach, funkcjach obsługi zdarzeń lub callbackach:
1// ŹLE - powoduje nieskończoną pętlę renderowania
2function BadComponent() {
3 const navigate = useNavigate();
4
5 if (!userIsAuthenticated) {
6 navigate('/login'); // To zostanie wywołane podczas każdego renderowania!
7 }
8
9 return <div>Chroniona zawartość</div>;
10}
11
12// DOBRZE - używa useEffect
13function GoodComponent() {
14 const navigate = useNavigate();
15
16 useEffect(() => {
17 if (!userIsAuthenticated) {
18 navigate('/login');
19 }
20 }, [navigate, userIsAuthenticated]);
21
22 return <div>Chroniona zawartość</div>;
23}2. Zagubione dane stanu
Pamiętaj, że dane przekazywane przez navigate w state są gubione przy odświeżeniu strony. Dla danych, które muszą przetrwać odświeżenie, używaj parametrów URL lub przechowuj je w lokalnym magazynie:
1// Dane zostaną utracone przy odświeżeniu
2navigate('/dashboard', { state: { importantData: data } });
3
4// Lepiej dla ważnych danych
5// 1. Użyj parametrów URL
6navigate(`/dashboard?dataId=${dataId}`);
7
8// 2. Lub zapisz w localStorage
9localStorage.setItem('importantData', JSON.stringify(data));
10navigate('/dashboard');3. Problemy z asynchronicznymi przekierowaniami
Czasami chcemy przekierować użytkownika po zakończeniu operacji asynchronicznej. Uważaj na potencjalne problemy, gdy komponent zostanie odmontowany przed zakończeniem operacji:
1function AsyncNavigation() {
2 const navigate = useNavigate();
3 const [isLoading, setIsLoading] = useState(false);
4
5 const handleClick = async () => {
6 setIsLoading(true);
7
8 try {
9 // Operacja asynchroniczna
10 await someAsyncOperation();
11
12 // Komponent może być już odmontowany w tym momencie!
13 // Jeśli używasz React 18 z Strict Mode, może to powodować problemy
14 navigate('/success');
15 } catch (error) {
16 setIsLoading(false);
17 // Obsłuż błąd
18 }
19 };
20
21 return (
22 <button onClick={handleClick} disabled={isLoading}>
23 {isLoading ? 'Ładowanie...' : 'Wykonaj operację'}
24 </button>
25 );
26}Rozwiązaniem jest użycie flag do śledzenia, czy komponent jest nadal zamontowany:
1function SafeAsyncNavigation() {
2 const navigate = useNavigate();
3 const [isLoading, setIsLoading] = useState(false);
4 const isMounted = useRef(true);
5
6 useEffect(() => {
7 return () => {
8 // Oznacz komponent jako odmontowany przy czyszczeniu
9 isMounted.current = false;
10 };
11 }, []);
12
13 const handleClick = async () => {
14 setIsLoading(true);
15
16 try {
17 await someAsyncOperation();
18
19 // Sprawdź, czy komponent jest nadal zamontowany
20 if (isMounted.current) {
21 navigate('/success');
22 }
23 } catch (error) {
24 if (isMounted.current) {
25 setIsLoading(false);
26 }
27 }
28 };
29
30 return (
31 <button onClick={handleClick} disabled={isLoading}>
32 {isLoading ? 'Ładowanie...' : 'Wykonaj operację'}
33 </button>
34 );
35}Podsumowanie
Nawigacja programistyczna jest potężnym narzędziem w arsenale developera React, pozwalającym na tworzenie zaawansowanych przepływów użytkownika i reagowanie na różne zdarzenia w aplikacji. Hook useNavigate oferuje elastyczne i intuicyjne API do kontrolowania nawigacji:
- Podstawowe przekierowania - proste przejścia między ścieżkami
- Nawigacja z parametrami - przekazywanie danych przez URL
- Przekazywanie stanu - niewidoczne dane dostępne na docelowej stronie
- Zastępowanie historii - kontrola nad stosem historii przeglądarki
- Warunkowe przekierowania - nawigacja oparta na logice biznesowej i odpowiedziach API
Używając nawigacji programistycznej, możesz budować bardziej dynamiczne i reagujące aplikacje, które płynnie przeprowadzają użytkownika przez różne kosmiczne przestrzenie twojej aplikacji.
Kod do tej lekcji: App.jsx
1import React from 'react';
2import { BrowserRouter, Routes, Route, NavLink } from 'react-router-dom';
3
4// Nawigacja z NavLink
5function Navigation() {
6 return (
7 <nav className="sidebar">
8 <h2>Space Station</h2>
9
10 <NavLink
11 to="/"
12 className={({ isActive }) => isActive ? 'nav-item active' : 'nav-item'}
13 >
14 Dashboard
15 </NavLink>
16
17 <NavLink
18 to="/crew"
19 className={({ isActive }) => isActive ? 'nav-item active' : 'nav-item'}
20 >
21 Załoga
22 </NavLink>
23
24 <NavLink
25 to="/missions"
26 className={({ isActive }) => isActive ? 'nav-item active' : 'nav-item'}
27 >
28 Misje
29 </NavLink>
30
31 <NavLink
32 to="/systems"
33 className={({ isActive }) => isActive ? 'nav-item active' : 'nav-item'}
34 >
35 Systemy
36 </NavLink>
37
38 <NavLink
39 to="/comms"
40 style={({ isActive }) => ({
41 color: isActive ? '#ffd700' : '#ffa500',
42 fontWeight: isActive ? 'bold' : 'normal',
43 background: isActive ? 'rgba(255, 215, 0, 0.2)' : 'transparent',
44 })}
45 className="nav-item"
46 >
47 Komunikacja
48 </NavLink>
49 </nav>
50 );
51}
52
53// Komponenty stron
54function Dashboard() {
55 return (
56 <div className="page">
57 <h1>Dashboard</h1>
58 <p>Witaj w centrum kontroli stacji kosmicznej!</p>
59 <div className="stats">
60 <div className="stat"><span>Załoga:</span> 6 osób</div>
61 <div className="stat"><span>Misje:</span> 3 aktywne</div>
62 <div className="stat"><span>Status:</span> Wszystko OK</div>
63 </div>
64 </div>
65 );
66}
67
68function Crew() {
69 return (
70 <div className="page">
71 <h1>Załoga</h1>
72 <ul className="crew-list">
73 <li>Anna Kowalska - Dowódca</li>
74 <li>Jan Nowak - Pilot</li>
75 <li>Maria Wiśniewska - Inżynier</li>
76 <li>Piotr Kowalczyk - Naukowiec</li>
77 </ul>
78 </div>
79 );
80}
81
82function Missions() {
83 return (
84 <div className="page">
85 <h1>Misje</h1>
86 <div className="mission-list">
87 <div className="mission">Apollo 25 - W trakcie</div>
88 <div className="mission">Artemis 12 - Planowanie</div>
89 <div className="mission">Europa Probe - Testowanie</div>
90 </div>
91 </div>
92 );
93}
94
95function Systems() {
96 return (
97 <div className="page">
98 <h1>Systemy</h1>
99 <div className="systems-grid">
100 <div className="system ok">Zasilanie:</div>
101 <div className="system ok">Życie:</div>
102 <div className="system ok">Nawigacja:</div>
103 <div className="system warn">Komunikacja:</div>
104 </div>
105 </div>
106 );
107}
108
109function Comms() {
110 return (
111 <div className="page">
112 <h1>Komunikacja</h1>
113 <p>Ostatni kontakt z Ziemią: 2 minuty temu</p>
114 <p>Siła sygnału: 87%</p>
115 </div>
116 );
117}
118
119function App() {
120 return (
121 <BrowserRouter>
122 <div className="app">
123 <Navigation />
124 <main>
125 <Routes>
126 <Route path="/" element={<Dashboard />} />
127 <Route path="/crew" element={<Crew />} />
128 <Route path="/missions" element={<Missions />} />
129 <Route path="/systems" element={<Systems />} />
130 <Route path="/comms" element={<Comms />} />
131 </Routes>
132 </main>
133 </div>
134 </BrowserRouter>
135 );
136}
137
138export 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. Do czego służy hook useNavigate w React Router v6?
2. Komponent Outlet w React Router służy do:
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Programowa nawigacja