So far our Pokedex app only has a single page.
Let's make this more interesting…
Let's make this more interesting…
We want to define distinct pages within our application.
The current page at a given time is defined by the URL of the browser tab.
The URL can contain parameters which are used by a page to show specific data.
React does not provide routing.
The most popular routing library in the React ecosystem is React Router.
npm install react-router-dom
function App() { return ( /* Let's route! */ ); }
import { RouterProvider, createBrowserRouter } from "react-router-dom"; const router = createBrowserRouter([]); function App() { return ( <RouterProvider router={router} /> ); }
createBrowserRouter creates an instance of a router that will be used.
import { RouterProvider, createBrowserRouter } from "react-router-dom"; import { Page1 } from "./Page1.tsx"; const router = createBrowserRouter([ { path: '/page1', element: <Page1 /> }, ]); function App() { return ( <RouterProvider router={router} /> ); }
Here we define a route with the path /page1 and tell it to render the Page1 component .
import { RouterProvider, createBrowserRouter } from "react-router-dom"; import { Page1 } from "./Page1.tsx"; import { Page2 } from "./Page2.tsx"; const router = createBrowserRouter([ { path: '/page1', element: <Page1 /> }, { path: '/page2', element: <Page2 /> }, ]); function App() { return ( <RouterProvider router={router} /> ); }
Page1.tsx
export function Page1() { return <p>This is page 1.</p>; }
The "page" is just a normal React component.
const router = createBrowserRouter( […, {path: '/cars/:carId', element: <CarDetailPage/>}]);
This route has a parameter carId .
CarDetailPage.tsx
import { useParams } from "react-router-dom"; export function CarDetailPage() { const { carId } = useParams<"carId">(); return <p>This is the car with the ID {carId}.</p>; }
import { useParams } from "react-router-dom"; export function CarDetailPage() { const { carId } = useParams<"carId">(); return <p>This is the car with the ID {carId}.</p>; }
The useParams Hook returns the parameters of the current route as an object.
import { RouterProvider, createBrowserRouter, Navigate } from "react-router-dom"; import { RouterProvider, createBrowserRouter } from "react-router-dom"; import { Page1 } from "./Page1.tsx"; import { Page2 } from "./Page2.tsx"; const router = createBrowserRouter([ {path: '/', element: <Navigate to="/page1" replace />}, {path: '/page1', element: <Page1/>}, {path: '/page2', element: <Page2/>} ]); function App() { return ( <RouterProvider router={router} /> ); }
With the Navigate component, a route can be redirected to another route.
With using the replace prop, no entry will be added to the browser history.
import { Link } from "react-router-dom"; function NavBar() { return ( <p> <Link to="/page1">Page 1</Link> | <Link to="/page2">Page 2</Link> </p> ); }
import { Link } from "react-router-dom"; function NavBar() { return ( <p> <Link to="/page1">Page 1</Link> | <Link to="/page2">Page 2</Link> </p> ); }
const router = createBrowserRouter([...]);
<RouterProvider router={router} />
{path: '/cars/:carId', element: <Car/>}
<Navigate to="/cars" replace />
<Link to="/cars/42">Go to Car 42</Link>
https://github.com/webplatformz/react-pokedex-vite/tree/chapter-05-routing/src/mockData
Think about what to do with the username input and display functionality.
Can we have the input on a Profile page but show the name on all pages?
App.tsx
const router = createBrowserRouter([{ path: "/", element: <Navigate to="/pokemon" replace />, }, { path: "/pokemon", element: <ListPage />, }, { path: "/pokemon/:pokemonName", element: <DetailPage />, }]); function App() { return <RouterProvider router={router} />; }
const router = createBrowserRouter([{ path: "/", element: <Navigate to="/pokemon" replace />, }, { path: "/pokemon", element: <ListPage />, }, { path: "/pokemon/:pokemonName", element: <DetailPage />, }]); function App() { return <RouterProvider router={router} />; }
const router = createBrowserRouter([{ path: "/", element: <Navigate to="/pokemon" replace />, }, { path: "/pokemon", element: <ListPage />, }, { path: "/pokemon/:pokemonName", element: <DetailPage />, }]); function App() { return <RouterProvider router={router} />; }
const router = createBrowserRouter([{ path: "/", element: <Navigate to="/pokemon" replace />, }, { path: "/pokemon", element: <ListPage />, }, { path: "/pokemon/:pokemonName", element: <DetailPage />, }]); function App() { return <RouterProvider router={router} />; }
const router = createBrowserRouter([{ path: "/", element: <Navigate to="/pokemon" replace />, }, { path: "/pokemon", element: <ListPage />, }, { path: "/pokemon/:pokemonName", element: <DetailPage />, }]); function App() { return <RouterProvider router={router} />; }
const router = createBrowserRouter([{ path: "/", element: <Navigate to="/pokemon" replace />, }, { path: "/pokemon", element: <ListPage />, }, { path: "/pokemon/:pokemonName", element: <DetailPage />, }]); function App() { return <RouterProvider router={router} />; }
Layout.tsx
import { Link } from "react-router-dom"; import { PropsWithChildren } from "react"; type LayoutProps = PropsWithChildren; export function Layout({ children }: LayoutProps) { return ( <div> <header> <nav> <Link to="/">Home</Link> | <Link to="/profile">Profile</Link> </nav> </header> <main>{children}</main> </div> ); }
ListPage.tsx
import { Layout } from "../../components/layout/Layout"; import { PokeList } from "../../components/poke-list/PokeList"; import { pokemonList } from "../../mockData/list"; function ListPage() { return ( <Layout> <PokeList pokemons={pokemonList.results} /> </Layout> ); } export { ListPage };
PokeListEntry.tsx
import { Link } from "react-router-dom"; interface PokeListEntryProps { name: string; } export function PokeListEntry({ name }: PokeListEntryProps) { return ( <li> <Link className="uppercase" to={`./${name}`}> {name} </Link> </li> ); }
import { Link } from "react-router-dom"; interface PokeListEntryProps { name: string; } export function PokeListEntry({ name }: PokeListEntryProps) { return ( <li> <Link className="uppercase" to={`./${name}`}> {name} </Link> </li> ); }
DetailPage.tsx
import { useParams } from "react-router-dom"; import { pokeDetails } from "../../mockData/details"; import { Layout } from "../../components/layout/Layout"; function DetailPage() { const { pokemonName } = useParams<"pokemonName">(); const pokemon = pokeDetails.find((p) => p.name === pokemonName); return ( <Layout> <h1 className="uppercase">{pokemonName}</h1> <img src={pokemon?.sprites.front_shiny} alt={pokemonName} /> </Layout> ); } export { DetailPage };
import { useParams } from "react-router-dom"; import { pokeDetails } from "../../mockData/details"; import { Layout } from "../../components/layout/Layout"; function DetailPage() { const { pokemonName } = useParams<"pokemonName">(); const pokemon = pokeDetails.find((p) => p.name === pokemonName); return ( <Layout> <h1 className="uppercase">{pokemonName}</h1> <img src={pokemon?.sprites.front_shiny} alt={pokemonName} /> </Layout> ); } export { DetailPage };
import { useParams } from "react-router-dom"; import { pokeDetails } from "../../mockData/details"; import { Layout } from "../../components/layout/Layout"; function DetailPage() { const { pokemonName } = useParams<"pokemonName">(); const pokemon = pokeDetails.find((p) => p.name === pokemonName); return ( <Layout> <h1 className="uppercase">{pokemonName}</h1> <img src={pokemon?.sprites.front_shiny} alt={pokemonName} /> </Layout> ); } export { DetailPage };
import { useParams } from "react-router-dom"; import { pokeDetails } from "../../mockData/details"; import { Layout } from "../../components/layout/Layout"; function DetailPage() { const { pokemonName } = useParams<"pokemonName">(); const pokemon = pokeDetails.find((p) => p.name === pokemonName); return ( <Layout> <h1 className="uppercase">{pokemonName}</h1> <img src={pokemon?.sprites.front_shiny} alt={pokemonName} /> </Layout> ); } export { DetailPage };
import { useParams } from "react-router-dom"; import { pokeDetails } from "../../mockData/details"; import { Layout } from "../../components/layout/Layout"; function DetailPage() { const { pokemonName } = useParams<"pokemonName">(); const pokemon = pokeDetails.find((p) => p.name === pokemonName); return ( <Layout> <h1 className="uppercase">{pokemonName}</h1> <img src={pokemon?.sprites.front_shiny} alt={pokemonName} /> </Layout> ); } export { DetailPage };
function MyComponent() { return (<> // … <NavLink to="/faq" className={({ isActive }) => isActive ? "activated" : ""} > FAQs </NavLink> </>); }
A NavLink is marked with the specified CSS class/style when its route is active.
const router = createBrowserRouter([ { path: "/", element: <Layout />, children: [ { path: "pokemon", element: <ListPage />, }, { path: "pokemon/:pokemonName", element: <DetailPage />, }, ], }, ]); function App() { return <RouterProvider router={router} />; }
const router = createBrowserRouter([ { path: "/", element: <Layout />, children: [ { path: "pokemon", element: <ListPage />, }, { path: "pokemon/:pokemonName", element: <DetailPage />, }, ], }, ]); function App() { return <RouterProvider router={router} />; }
function Layout() { return (<div> {/* Navbar… */} <Outlet /> </div>); }
Current URL: /cars?filter=Tesla
function MyComponent() { const { pathname, search } = useLocation(); console.log(`Pathname: ${pathname}`); /* Pathname: /cars */ console.log(`Query string: ${search}`); /* Query string: ?filter=Tesla */ /* … */ }
Returns the location object representing the current URL.
function MyComponent() { const navigate = useNavigate(); return <button onClick={() => { // Do something navigate('/home'); }}> Do something and then go to Home </button> }
Navigate programmatically
import { RouterProvider, createMemoryRouter } from "react-router-dom"; const router = createMemoryRouter(routes, { initialEntries: ["/", "/events/123"], initialIndex: 1, }); function MyComponent() { return <RouterProvider router={router} />; }
Use instead of BrowserRouter for tests.
We learned…