Navigator, we've reached the final stage of our journey through the Asteroid Belt! It's time to gather all the navigation techniques we've learned and build a complete routing system - a true Navigation Command Center for our cosmic fleet.
In this lesson, we'll see how all React Router elements work together in one cohesive application.
When building an application with full routing, we need to plan several layers:
In larger applications, it's worth organizing routing-related files in a logical structure:
1src/
2βββ App.jsx // Main component with BrowserRouter
3βββ routes/
4β βββ AppRoutes.jsx // Definition of all routes
5β βββ ProtectedRoute.jsx
6βββ context/
7β βββ AuthContext.jsx // Authentication context
8βββ layouts/
9β βββ MainLayout.jsx // Layout with navigation and Outlet
10β βββ DashboardLayout.jsx
11βββ pages/
12β βββ HomePage.jsx
13β βββ LoginPage.jsx
14β βββ MissionDetail.jsx
15β βββ NotFound.jsx
16βββ components/
17 βββ Navigation.jsx // NavLink menuSuch organization allows easy management of even extensive applications with dozens of routes.
The central element is the AuthProvider wrapping the entire application:
1function AuthProvider({ children }) {
2 const [user, setUser] = useState(null);
3
4 const login = (name) => setUser({ name });
5 const logout = () => setUser(null);
6
7 return (
8 <AuthContext.Provider value={{ user, login, logout }}>
9 {children}
10 </AuthContext.Provider>
11 );
12}
13
14// In App.jsx:
15function App() {
16 return (
17 <AuthProvider>
18 <BrowserRouter>
19 <Navigation />
20 <Routes>
21 {/* public and protected routes */}
22 </Routes>
23 </BrowserRouter>
24 </AuthProvider>
25 );
26}The AuthProvider must be outside BrowserRouter so that the authentication context is available in all route components.
The navigation component combines NavLink (with active styles) with AuthContext (conditional display of options):
1function Navigation() {
2 const { user, logout } = useAuth();
3
4 return (
5 <nav>
6 <NavLink to="/" className={({isActive}) => isActive ? 'active' : ''}>
7 Home
8 </NavLink>
9 <NavLink to="/missions" className={({isActive}) => isActive ? 'active' : ''}>
10 Missions
11 </NavLink>
12
13 {user ? (
14 <>
15 <NavLink to="/dashboard" className={({isActive}) => isActive ? 'active' : ''}>
16 Dashboard
17 </NavLink>
18 <span>Logged in: {user.name}</span>
19 <button onClick={logout}>Logout</button>
20 </>
21 ) : (
22 <NavLink to="/login">Login</NavLink>
23 )}
24 </nav>
25 );
26}This way the navigation automatically responds to the authentication state - a logged-in user sees the Dashboard and logout button, while an unauthenticated user sees the login link.
The missions section uses nested routes with Outlet:
1function MissionsLayout() {
2 return (
3 <div className="missions-layout">
4 <aside>
5 <h3>Missions</h3>
6 <NavLink to="/missions/apollo">Apollo</NavLink>
7 <NavLink to="/missions/artemis">Artemis</NavLink>
8 <NavLink to="/missions/voyager">Voyager</NavLink>
9 </aside>
10 <main>
11 <Outlet />
12 </main>
13 </div>
14 );
15}
16
17// In route configuration:
18<Route path="/missions" element={<MissionsLayout />}>
19 <Route index element={<MissionsList />} />
20 <Route path=":missionId" element={<MissionDetail />} />
21</Route>The index route (
<Route index />) renders the default content (missions list) when the user is at /missions. The dynamic parameter :missionId handles specific mission details.Here's what the full route configuration looks like combining all the techniques we've learned:
1function App() {
2 return (
3 <AuthProvider>
4 <BrowserRouter>
5 <Navigation />
6 <Routes>
7 {/* Home page */}
8 <Route path="/" element={<HomePage />} />
9
10 {/* Login */}
11 <Route path="/login" element={<LoginPage />} />
12
13 {/* Nested routes with layout */}
14 <Route path="/missions" element={<MissionsLayout />}>
15 <Route index element={<MissionsList />} />
16 <Route path=":missionId" element={<MissionDetail />} />
17 </Route>
18
19 {/* Protected route */}
20 <Route path="/dashboard" element={
21 <ProtectedRoute>
22 <DashboardPage />
23 </ProtectedRoute>
24 } />
25
26 {/* 404 handling */}
27 <Route path="*" element={<NotFound />} />
28 </Routes>
29 </BrowserRouter>
30 </AuthProvider>
31 );
32}The order of elements matters:
/, /login, /missions) are matched firstpath="*" route must be last - it catches everything that doesn't match any defined routeThe login page uses useNavigate and useLocation to redirect the user back to the page they were redirected from:
1function LoginPage() {
2 const [name, setName] = useState('');
3 const { login } = useAuth();
4 const navigate = useNavigate();
5 const location = useLocation();
6
7 // Path the user was redirected from
8 const from = location.state?.from?.pathname || '/dashboard';
9
10 const handleSubmit = (e) => {
11 e.preventDefault();
12 if (name.trim()) {
13 login(name);
14 navigate(from, { replace: true }); // replace: true removes /login from history
15 }
16 };
17
18 return (
19 <form onSubmit={handleSubmit}>
20 <input value={name} onChange={(e) => setName(e.target.value)} />
21 <button type="submit">Log In</button>
22 </form>
23 );
24}The
replace: true option in navigate is crucial - thanks to it, the user won't return to the login page after clicking "Back" in the browser.The Not Found page contains a link back to the home page:
1function NotFound() {
2 return (
3 <div className="not-found">
4 <h1>404</h1>
5 <p>Page not found</p>
6 <Link to="/">Return to home page</Link>
7 </div>
8 );
9}A complete application with routing in React combines the following elements:
| Element | Role | Hook / Component | |---------|------|-------------------| | Route container | Enables routing | BrowserRouter | | Route definition | Maps URLs to components | Routes, Route | | Navigation | Links with active styles | NavLink | | Nesting | Shared layout | Outlet, Route children | | Dynamic parameters | Element details | useParams | | Programmatic navigation | Redirect after action | useNavigate | | Location state | Passing hidden data | useLocation | | Route protection | Enforcing login | ProtectedRoute + Navigate | | Error handling | Non-existent pages | Route path="*" | | Auth context | Global user data | createContext + useContext |
Try the complete application in the editor below - log in, navigate between missions, check the Dashboard, and test the 404 page!