Chapter 07 - Finding the Path
Chapter 07 - Finding the Path
Hey everyone! Welcome back to Namaste React!
Our app currently has only one page. Real apps have many pages — Home, About, Contact, Restaurant Menu, Cart. Today we learn React Router v6 — how to build multi-page experiences WITHOUT page reloads!
What we will cover:
- What is Client-Side Routing?
- React Router v6 setup
- createBrowserRouter and RouterProvider
- Link vs NavLink vs <a> tag
- useParams — dynamic routes
- useNavigate — programmatic navigation
- Nested Routes and Outlet
- Protected/Private Routes
- 404 Not Found page
- Lazy loading routes with React.lazy + Suspense
- Interview Questions
1. Client-Side Routing vs Server-Side Routing
┌─────────────────────────────────────────────────────────────┐ │ SERVER-SIDE vs CLIENT-SIDE ROUTING │ └─────────────────────────────────────────────────────────────┘ SERVER-SIDE ROUTING (Traditional): ===================================== User clicks "About" link ↓ Browser sends HTTP request: GET /about ↓ Server receives request ↓ Server returns a NEW HTML page ↓ Browser LOADS the new page (full page reload!) ↓ User sees About page Problems: - Full page reload feels slow and janky - All assets (CSS, JS) re-downloaded - Scroll position lost - Animation state lost CLIENT-SIDE ROUTING (React Router): ====================================== User clicks "About" link ↓ React Router intercepts the click ↓ React Router updates the URL (using History API) ↓ React renders the <About /> component ↓ User sees About page INSTANTLY! (no server request!) Benefits: ✅ Instant navigation (no network request!) ✅ Smooth transitions and animations ✅ Scroll position can be preserved ✅ State persists across navigation ✅ Feels like a native app!
2. React Router v6 Setup
// 1. Install React Router:
npm install react-router-dom
// 2. Import and configure:
import {
createBrowserRouter,
RouterProvider,
Route,
Routes,
Link,
NavLink,
Outlet,
useParams,
useNavigate,
Navigate,
} from "react-router-dom";
// 3. Create a router configuration:
const appRouter = createBrowserRouter([
{
path: "/",
element: <AppLayout />, // wrapper layout
children: [
{
path: "/",
element: <Body />, // home page
},
{
path: "/about",
element: <About />,
},
{
path: "/contact",
element: <Contact />,
},
{
path: "/restaurant/:resId", // ← dynamic segment
element: <RestaurantMenu />,
},
{
path: "/cart",
element: <Cart />,
},
],
errorElement: <Error />, // ← catches 404s and router errors
},
]);
// 4. Provide the router to your app:
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<RouterProvider router={appRouter} />);
3. Two Types of Routing in React Router
This is a very popular interview question — React Router gives you two types of routers to choose from!
┌─────────────────────────────────────────────────────────────┐ │ TWO TYPES OF ROUTING IN REACT ROUTER │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. createBrowserRouter → /about │ │ 2. createHashRouter → /#/about │ │ │ └─────────────────────────────────────────────────────────────┘
TYPE 1: createBrowserRouter (History API Routing)
==================================================
import { createBrowserRouter } from "react-router-dom";
const appRouter = createBrowserRouter([
{ path: "/", element: <Home /> },
{ path: "/about", element: <About /> },
{ path: "/cart", element: <Cart /> },
]);
// URLs look like:
// https://myapp.com/
// https://myapp.com/about
// https://myapp.com/cart
// Uses the HTML5 History API (pushState / replaceState)
// Clean, professional-looking URLs — no # symbol!
TYPE 2: createHashRouter (Hash-based Routing)
==============================================
import { createHashRouter } from "react-router-dom";
const appRouter = createHashRouter([
{ path: "/", element: <Home /> },
{ path: "/about", element: <About /> },
{ path: "/cart", element: <Cart /> },
]);
// URLs look like:
// https://myapp.com/#/
// https://myapp.com/#/about
// https://myapp.com/#/cart
// Uses the URL hash (the # part)
// Everything after # never gets sent to the server!
Wait — why does it matter whether the URL is sent to the server?
Great question! Let me explain with an example:
THE PROBLEM with createBrowserRouter on static hosting:
=========================================================
User visits: https://myapp.com/about
↓
Browser sends request to server: GET /about
↓
Server looks for a file at /about
↓
File doesn't exist! → Server returns 404 ❌
WHY? Because the server doesn't know about React routing.
It only knows about files on disk. /about is a React route,
not a real file!
FIX: Configure the server to always serve index.html
for ALL routes. (Nginx, Apache, Vercel all support this)
React Router then takes over and shows the right page. ✅
WHY createHashRouter solves this WITHOUT server config:
=========================================================
User visits: https://myapp.com/#/about
↓
Browser sends request to server: GET / ← only the root!
↓
Server serves index.html ✅ (root always exists)
↓
React app loads
↓
React Router reads the #/about part from the URL
↓
React Router shows the About page ✅
The # and everything after it is NEVER sent to the server.
It's handled 100% in the browser by JavaScript!
WHEN TO USE WHICH? =================== createBrowserRouter: ✅ Production apps with proper server configuration ✅ Vercel, Netlify, AWS — they handle this automatically ✅ Clean URLs look professional ✅ SEO friendly (search engines can index /about) ❌ Needs server configured to serve index.html for all routes createHashRouter: ✅ Static hosting (GitHub Pages, simple file servers) ✅ No server configuration needed — works out of the box ✅ Good for demos and internal tools ❌ Ugly URLs with # symbol ❌ Not great for SEO (search engines ignore the # part) In simple words: createBrowserRouter = "My server is properly configured" createHashRouter = "I just have simple/static hosting"
4. Layout with Outlet
// AppLayout — the persistent shell around all pages
// Header and Footer stay, only the middle changes!
const AppLayout = () => {
return (
<div className="app">
<Header /> {/* Always visible — doesn't re-mount on navigation */}
{/* Outlet renders whichever child route matches */}
<Outlet /> {/* <Body /> or <About /> or <Contact /> etc. */}
<Footer /> {/* Always visible */}
</div>
);
};
// How Outlet works:
// URL = "/" → Outlet renders <Body />
// URL = "/about" → Outlet renders <About />
// URL = "/contact" → Outlet renders <Contact />
// Header and Footer stay mounted throughout — no remount!
4. Link vs NavLink vs <a> tag
// ❌ Regular <a> tag — causes full page reload!
<a href="/about">About</a>
// This sends a real HTTP request and reloads the entire page.
// Loses all React state. Never use for internal navigation!
// ✅ Link — client-side navigation, NO page reload
import { Link } from "react-router-dom";
<Link to="/about">About</Link>
// Renders as an <a> tag in the DOM, but intercepts the click.
// Uses History API pushState() to change the URL.
// React Router re-renders without a page reload!
// ✅ NavLink — like Link, but with active styling!
import { NavLink } from "react-router-dom";
<NavLink
to="/about"
className={({ isActive }) => isActive ? "nav-link active" : "nav-link"}
>
About
</NavLink>
// NavLink automatically knows if it's the CURRENT page!
// You get isActive in the className function.
// Adds "active" class when URL matches — perfect for nav menus!
// NavLink in our Header:
const Header = () => {
return (
<div className="header">
<div className="logo-container">
<img src={logo} className="logo" />
</div>
<nav className="nav-items">
<ul>
<li><NavLink to="/">Home</NavLink></li>
<li><NavLink to="/about">About Us</NavLink></li>
<li><NavLink to="/contact">Contact Us</NavLink></li>
<li><NavLink to="/cart">Cart 🛒</NavLink></li>
</ul>
</nav>
</div>
);
};
5. useParams — Dynamic Routes
// ROUTE DEFINITION with dynamic segment:
{
path: "/restaurant/:resId", // ← :resId is a dynamic segment
element: <RestaurantMenu />,
}
// NAVIGATION to a specific restaurant:
<Link to={`/restaurant/${restaurant.info.id}`}>
<RestaurantCard resData={restaurant} />
</Link>
// If restaurant.info.id = "12345", URL becomes: /restaurant/12345
// READING the dynamic segment in RestaurantMenu:
import { useParams } from "react-router-dom";
const RestaurantMenu = () => {
const { resId } = useParams(); // reads the :resId from the URL!
const [restaurant, setRestaurant] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchMenu(resId);
}, [resId]); // re-fetch when resId changes!
const fetchMenu = async (id) => {
const response = await fetch(
`https://www.swiggy.com/dapi/menu/pl?page-type=REGULAR_MENU&restaurantId=${id}`
);
const json = await response.json();
setRestaurant(json?.data?.cards?.[2]?.card?.card?.info);
setLoading(false);
};
if (loading) return <Shimmer />;
return (
<div className="menu">
<h1>{restaurant?.name}</h1>
<p>{restaurant?.cuisines?.join(", ")}</p>
<p>{restaurant?.avgRating} ⭐</p>
</div>
);
};
6. useNavigate — Programmatic Navigation
// Sometimes you need to navigate without a link click.
// Example: After login, redirect to home. After checkout, go to confirmation.
import { useNavigate } from "react-router-dom";
const LoginPage = () => {
const navigate = useNavigate();
const handleLogin = async () => {
const success = await loginUser(credentials);
if (success) {
navigate("/"); // Go to home after login
navigate("/dashboard"); // Or go to dashboard
navigate(-1); // Go BACK (like browser back button!)
navigate("/", { replace: true }); // Replace history (can't go back)
}
};
return (
<div>
<button onClick={handleLogin}>Login</button>
</div>
);
};
// navigate() options:
navigate("/path") // Push to history
navigate("/path", { replace: true }) // Replace current history entry
navigate(-1) // Go back
navigate(1) // Go forward
navigate("/path", { state: { userId: 123 } }) // Pass state (accessible via useLocation)
7. Protected Routes
// Some pages should only be accessible when logged in!
// APPROACH 1: Component wrapper
const ProtectedRoute = ({ children }) => {
const { isAuthenticated } = useAuth(); // your auth hook
if (!isAuthenticated) {
return <Navigate to="/login" replace />; // redirect to login!
}
return children;
};
// Usage in router:
{
path: "/cart",
element: (
<ProtectedRoute>
<Cart />
</ProtectedRoute>
)
}
// APPROACH 2: In the router config
{
path: "/cart",
element: isAuthenticated ? <Cart /> : <Navigate to="/login" />
}
// The Navigate component performs a redirect when rendered.
// replace={true} removes the current entry from history
// so the user can't press "back" to get to the protected page!
8. Error / 404 Page
// Handles both 404s (unknown routes) and router errors
import { useRouteError } from "react-router-dom";
const Error = () => {
const err = useRouteError();
return (
<div className="error-page">
<h1>Oops!</h1>
<h2>Something went wrong!</h2>
<p>{err.status}: {err.statusText || err.message}</p>
<Link to="/">Go back home</Link>
</div>
);
};
// In router config:
const appRouter = createBrowserRouter([
{
path: "/",
element: <AppLayout />,
errorElement: <Error />, // ← shows for ALL errors in this tree
children: [...]
}
]);
// useRouteError() returns:
// For 404: { status: 404, statusText: "Not Found", ... }
// For thrown errors: { message: "Something broke", ... }
9. Lazy Loading Routes
// Without lazy loading: EVERYTHING bundled into one large JS file.
// User downloads all pages on first load — even pages they never visit!
// WITH LAZY LOADING: each page in its own chunk.
// Browser downloads a page's code ONLY when the user navigates there!
import { lazy, Suspense } from "react";
// Lazy import — code not loaded until the component is rendered!
const About = lazy(() => import("./components/About"));
const Contact = lazy(() => import("./components/Contact"));
const Cart = lazy(() => import("./components/Cart"));
// Wrap in Suspense — shows fallback while chunk loads:
const appRouter = createBrowserRouter([
{
path: "/",
element: <AppLayout />,
children: [
{ path: "/", element: <Body /> },
{
path: "/about",
element: (
<Suspense fallback={<p>Loading...</p>}>
<About />
</Suspense>
)
},
]
}
]);
// Or wrap Outlet in Suspense (applies to ALL lazy children):
const AppLayout = () => (
<div>
<Header />
<Suspense fallback={<Shimmer />}>
<Outlet />
</Suspense>
<Footer />
</div>
);
How lazy loading helps bundle size:
=====================================
Without lazy: bundle.js = 500KB (all pages!)
With lazy: bundle.js = 100KB (only what's needed now)
about.chunk.js = 20KB (loaded when user goes to /about)
cart.chunk.js = 40KB (loaded when user goes to /cart)
User downloads 100KB on first load instead of 500KB! 🚀
Interview Questions
Q: What are the two types of routing in React Router? What is the difference?
"React Router provides createBrowserRouter and createHashRouter. createBrowserRouter uses the HTML5 History API and produces clean URLs like /about — but requires the server to serve index.html for all routes, otherwise you get a 404 on refresh. createHashRouter uses the URL hash, so URLs look like /#/about. The hash part is never sent to the server, meaning it works on any static host without any server configuration. For production apps on Vercel or Netlify I always use createBrowserRouter since those platforms handle the server config automatically. For quick demos on GitHub Pages I'd use createHashRouter."
Q: What is the difference between client-side and server-side routing?
"Server-side routing sends an HTTP request to the server for each navigation, which responds with a full HTML page — causing a full page reload. Client-side routing intercepts navigation, uses the History API to update the URL, and swaps components in React without a server request. React Router is a client-side router — navigation is instant, state is preserved, and it feels like a native app."
Q: What is the difference between Link and NavLink in React Router?
"Both perform client-side navigation without page reload. The difference is that NavLink knows if its destination is the currently active route. You can pass a className function that receives an isActive boolean, letting you apply different styles to the active link. This is perfect for navigation menus where you want to highlight the current page."
Q: What is useParams?
"useParams is a React Router hook that reads dynamic URL segments from the current route. If the route is /restaurant/:resId and the URL is /restaurant/12345, then useParams() returns { resId: '12345' }. It's commonly used on detail pages to know which item to fetch from the API."
Q: What is Outlet in React Router?
"Outlet is a component that renders the currently matched child route's component. It's used in layout components like AppLayout — the Header and Footer are always rendered, but Outlet renders whatever page matches the URL. When the URL changes, only the Outlet re-renders; the Header and Footer stay mounted and never re-render."
Q: What is React.lazy and how does it work with routing?
"React.lazy enables code splitting by dynamically importing a component only when it's first rendered. Combined with routing, you can lazy-load each page component separately — the browser only downloads the code for a page when the user navigates there. You must wrap lazy components in Suspense which shows a fallback (like a spinner) while the chunk is being downloaded."
Key Points to Remember
| Concept | Key Takeaway |
|---|---|
| Client-side routing | No page reload. URL changes via History API. React swaps components. |
| createBrowserRouter | Clean URLs (/about). Needs server to serve index.html for all routes. |
| createHashRouter | Hash URLs (/#/about). Works on any static host — no server config needed. |
| createBrowserRouter | Define routes as an array of objects. More powerful than JSX routes. |
| Outlet | Placeholder in layout components. Renders whichever child route matches. |
| Link | For internal navigation. Renders as <a> but doesn't reload the page. |
| NavLink | Like Link but gives isActive — for highlighting current page in nav. |
| useParams | Read :dynamic segments from the URL (e.g., restaurant ID). |
| useNavigate | Programmatic navigation. navigate("/path") or navigate(-1) for back. |
| Protected routes | Wrap component in ProtectedRoute that redirects with <Navigate> if not auth'd. |
| errorElement | Shown for 404s and errors. useRouteError() gets the error details. |
| React.lazy + Suspense | Code split pages. Each page downloads only when visited. |
What's Next?
In Chapter 08, we go back in time and learn Class Components — the old way of writing React before hooks. Understanding them is important for interviews and for reading older codebases. We also learn Error Boundaries, which are ONLY possible with class components!
Keep coding, keep learning! See you in the next one!
Post a Comment