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.tsximport { 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.tsximport { 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.tsimport { 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
. Thename
from the URL will be available inuseParams().name
.The third route above will match
/minions/123
and/minions/abc
. Theid
from the URL will be available inuseParams().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>}