React Router

When we start making single-page apps with dynamic interfaces (using React, Angular, Ember amongst others) we don't want to reload the page. But we still want the URL to reflect which part of the UI a user is looking at. This is often called the view.

When the view changes, the user sees a different URL. The URL can be linked to from outside the site and the browser will load the correct view. However, the page is only loaded from the server once: everything else is done client-side, with API requests to communicate with the server as required.

In React, we can manage client-side routing using React Router.

Routing to components

We're going to be using React Router on the web (it can also be used in other environments) so we're going to install the react-router-dom npm module.

// client/routes.tsx
import { createRoutesFromElements, Route } from 'react-router'
import Home from './Home'
import About from './About'
import Team from './Team'
const routes = createRoutesFromElements(
<Route path="/" element={<App />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="team" element={<Team />} />
</Route>
)
export default routes

When the user visits any URL on your site, they'll end up looking at the App component and one of the route components inside of it depending on the URL.

Each time you add a new route, you'll need to import the component that it references. Visitors to https://localhost:3000/about will see the About component and not Home.

Unlike server-side rendering, BrowserRouter will choose the most specific matching route, and so the order of routes doesn't matter.

When nesting other routes inside of the App route, we need to define where in App these other components will appear. This is done with an Outlet:

// App.tsx
import { Outlet } from 'react-router'
import Nav from './Nav'
function App() {
return (
<div className="main-container">
<header>
<h1>Welcome!</h1>
<Nav />
</header>
<Outlet /> {/* <- where nested views will be rendered */}
</div>
)
}
export default App

We include our router at the root of our project (in the client/index) using createBrowserRouter and RouterProvider as follows:

// client/index.ts
import { createRoot } from 'react-dom/client'
import { createBrowserRouter } from 'react-router'
import { RouterProvider } from 'react-router/dom'
import routes from './routes'
const router = createBrowserRouter(routes)
document.addEventListener('DOMContentLoaded', () => {
createRoot(document.getElementById('app')).render(
<RouterProvider router={router} />
)
})

Aside: we choose to use BrowserRouter for our sites but there are other routers available in react-router that are useful in other situations. For example, we use MemoryRouter during testing.

Linking to routes

We can create links between our routes, but we don't do it using an anchor tag (<a href=""></a>). Instead, React Router provides us with a Link component:

import { Link } from 'react-router-dom'
// ...
function Home() {
return (
<div>
Welcome! Choose from the following pages:
<ul>
<li>
<Link to="/wombats">All about wombats</Link>
</li>
<li>
<Link to="/aardvarks">All about aardvarks</Link>
</li>
<li>
<Link to="/jerboas">All about jerboas</Link>
</li>
</ul>
</div>
)
}

The to prop in each Link tells us which route it's going to.

Using the Link prevents the browser from firing off a request to the server every time a URL like https://localhost:3000/about is visited and instead navigates on the client-side.

Route parameters

To specify a route parameter (a variable in the URL which will be available in the route's component), React Router uses a similar scheme to many other libraries: a colon followed by the variable name.

<Routes>
<Route path="/" element={<Home />} />
<Route path="smurfs/:name" element={<Smurfs />} />
<Route path="minions/:id" element={<Minions />} />
</Routes>
  • The second route above will match /smurfs/123 and /smurfs/abc. The name from the URL will be available in useParams().name.

  • The third route above will match /minions/123 and /minions/abc. The id from the URL will be available in useParams().id.

After importing the useParams hook we can use the URL parameters like this.

import { useParams } from 'react-router-dom'
// ...
function Smurfs() {
const { name } = useParams()
// ...
return <p>This smurf is called {name}.</p>
}

Resources