We have updated the content of our program. To access the current Software Engineering curriculum visit curriculum.turing.edu.
React Router v6
Note
React Router just released the newest version - version 6
. This lesson has been updated to reflect the changes for v6. If you’re looking for the v5 lesson, go here. Interested in upgrading your v5 app to v6? Start here.
Learning Goals:
- Understand and articulate the need for routing
- Be able to confidently implement React Router in a project
- Utilize URL params to build dynamic routes
Vocab
BrowserRouter
A <Router> that uses the HTML5 history API (pushState, replaceState and the popstate event) to keep your UI in sync with the URLRouter
is a component provided by React Router.This component is responsible for keeping track of the current location (the URL) and re-rendering your app whenever that location changes.And is shared by all router components (like<BrowserRouter>
).Link
Links provide declarative, accessible navigation around your applicationNavLink
A special version of the <Link> that will add styling attributes to the rendered element when it matches the current URL.Route
Its most basic responsibility is to render some UI when a location matches the route’s pathRoutes
A component that wraps your Route components that selects the best path matchOutlet
A component that renders the next match in a set of matches. it must exist in the parent component when nesting routesuseParams
A hook that allows us to gain access to a Route’s params
Prework
Before the lesson, complete the prework.
You might also find it helpful to watch this video walkthrough before the lesson, but it’s not required.
Prework Review
In small groups, discuss the following questions via this Jamboard:
- Why use Router?
- Describe the high-level process of setting up Router in a project (packages to install, basic component needed)
- Describe the following components:
- Route
- Routes
- Link
- NavLink
- Outlet
Why Routing?
Routing refers to keeping a webpage up to date with the current url, and vice-versa.
Most of the apps you’ve written so far have been single-page applications. One HTML page whose content is updated through user interactions and JS. These DO NOT use routing. They work fine, but put some limits on the user experience of our applications.
Here are some advantages routing can provide:
- Users can use urls to bookmark pages.
- Users can use the back or forward button.
- Users can easily share content from a page in the app.
If you have written a multi-page application, you may have wrestled with Webpack configs in order to get all your pages built successfully. Fortunately, routing with React is easy! We just need to use a library called React Router.
React Router allows us to conditionally render components based on the current url.
Rather than tell you about how Router works, we’ll work through a series of exercises and examples. We’ll be using this repo to solve a series of challenges listed below.
Installation Instructions
0. Clone repo & review codebase
git clone https://github.com/turingschool-examples/react-router-v6
cd react-router-v6
npm i
npm start
The App is not fully put together. It has a series of components that will serve as building blocks of the final component. You won’t be building out new components, but you will be editing existing ones. Get oriented with the application. Check out all the components, try and write a short summary of what each is doing.
Installing Router
1. The first step is installing react router
npm install react-router-dom
2. Once you have React Router installed, import your chosen Router
To use React Router, we need to wrap any components that will use a React Router-provided-component in some kind of Router component. We’ll use a Browser Router, since our app will be used in the browser. This Router provides access to the HTML5 History API. But we won’t worry about those details just yet.
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './Components/App/App';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<React.StrictMode>
<App />
</React.StrictMode>
</BrowserRouter>
);
Rendering the Home component
3. Now, let’s tell React Router what to render at the base URL (‘/’)
// App.js
import './App.css';
import { Routes, Route } from 'react-router-dom';
import Home from '../Home/Home';
function App() {
return (
<main className="App">
<nav>
<a href="/puppies" className="nav">Puppies</a>
<a href="/sharks" className="nav">Sharks</a>
</nav>
<h1>Puppies or Sharks?</h1>
<Routes>
<Route path="/" element={<Home />}/>
</Routes>
</main>
);
}
export default App;
Let’s explore
- What happens if you remove the line that starts with
import { Routes...
? What does the error message say? - What happens if you remove the
Routes
component? What does the error message say?
We picked /
for the path in the route because it designates that there won’t be anything after the URL’s domain name. This represents the base URL.
Exercise # 1: Render Puppies
Your goal is click on the word Puppies and see a grid of 9 puppies on the DOM. The page should look something like the picture on the lesson plan. While you may change components as needed, you shouldn’t outright delete content from the page to achieve this.
Take 10 minutes in pairs to get the puppies rendering
Hints:
- Use the Creatures component. Formatting and styling is handled for you.
- What additional react-router components should you use? Do any current components need to change?
- How do you pass props into a component rendered by a
<Route />
?
4. First, let’s update the links to NavLink components
// App.js
import './App.css';
import { Routes, Route, NavLink } from 'react-router-dom';
import Home from '../Home/Home';
function App() {
return (
<main className="App">
<nav>
<NavLink to="/puppies" className="nav">Puppies</NavLink>
<NavLink to="/sharks" className="nav">Sharks</NavLink>
</nav>
<h1>Puppies or Sharks?</h1>
<Routes>
<Route path="/" element={<Home />}/>
</Routes>
</main>
);
}
export default App;
Let’s explore
- Change the
<NavLink />
components to<Link />
components. What happens? - When might you choose a
<Link />
over a<NavLink />
?
5. Now, let’s tell Router what to do at ‘/puppies’
// App.js
import './App.css';
import { Routes, Route, NavLink } from 'react-router-dom';
import Home from '../Home/Home';
import Creatures from '../Creatures/Creatures';
function App() {
return (
<main className="App">
<nav>
<NavLink to="/puppies" className="nav">Puppies</NavLink>
<NavLink to="/sharks" className="nav">Sharks</NavLink>
</nav>
<h1>Puppies or Sharks?</h1>
<Routes>
<Route path="/" element={<Home />}/>
<Route path="/puppies" element={<Creatures creatureType='puppies'/>} />
</Routes>
</main>
);
}
export default App;
Let’s explore
- Why doesn’t the
<Home />
component render when you’re on the/puppies
path? - Does order matter? Try switching the two
<Route />
components. What happens? - How do you pass props to components from inside a
<Route />
?
6. Let’s update the Creatures component so it actually shows puppies
// Creatures.js
import './Creatures.css';
import { getCreaturesData } from '../../data/animalData';
const Creatures = ({ creatureType }) => {
const creatureImages = getCreaturesData(creatureType).map(creature => {
const { id, image } = creature;
return <img src={image} key={id} id={id} className="app-img"/>
});
return (
<>
<h1>{creatureType}!</h1>
{creatureImages}
</>
)
}
export default Creatures;
Rendering the Sharks
7. On your own, make the Sharks button render sharks
// App.js
import './App.css';
import { Routes, Route, NavLink } from 'react-router-dom';
import Home from '../Home/Home';
import Creatures from '../Creatures/Creatures';
function App() {
return (
<main className="App">
<nav>
<NavLink to="/puppies" className="nav">Puppies</NavLink>
<NavLink to="/sharks" className="nav">Sharks</NavLink>
</nav>
<h1>Puppies or Sharks?</h1>
<Routes>
<Route path="/" element={<Home />}/>
<Route path="/puppies" element={<Creatures creatureType='puppies'/>} />
<Route path="/sharks" element={<Creatures creatureType='sharks'/>} />
</Routes>
</main>
);
}
export default App;
Let’s explore
Hmmm…two of those <Route />
components are looking quite similar. I wonder if there is a way to make that more dynamic. Take a minute to consider what would we need in order to turn those two <Route />
components into one.
Dynamic Routing
In this exercise, we’ll explore two ways to achieve dynamic routing in order to obtain an animal and its ID from the route: first, we’ll use the Outlet
, and then we’ll show you how to implement a dynamic route without using an outlet.
8. Let’s start by making a dynamic path
// App.js
import './App.css';
import { Routes, Route, NavLink } from 'react-router-dom';
import Home from '../Home/Home';
import Creatures from '../Creatures/Creatures';
function App() {
return (
<main className="App">
<nav>
<NavLink to="/puppies" className="nav">Puppies</NavLink>
<NavLink to="/sharks" className="nav">Sharks</NavLink>
</nav>
<h1>Puppies or Sharks?</h1>
<Routes>
<Route path="/" element={<Home />}/>
<Route path="/:animal" element={<Creatures creatureType='puppies'/>} />
</Routes>
</main>
);
}
export default App;
Let’s explore
- In your app, click on the
Sharks
button. What renders to the page? What shows up in the URL? Why? - How can we tell the
<Creatures />
component which animal we’ve selected?
9. Let’s console.log some stuff:
// Creatures.js
import './Creatures.css';
import { getCreaturesData } from '../../data/animalData';
import { useParams } from 'react-router-dom';
const Creatures = ({ creatureType }) => {
console.log(useParams())
const creatureImages = getCreaturesData(creatureType).map(creature => {
const { id, image } = creature;
return <img src={image} key={id} id={id} className="app-img"/>
});
return (
<>
<h1>{creatureType}!</h1>
{creatureImages}
</>
)
}
export default Creatures;
Let’s explore
- What console.logged? Where does the key:value pair come from?
- Click between the
Puppies
andSharks
buttons. How is the logged object changing? - Manually type something random into the URL, like
localhost:3000/potatoes
. What logs?
10. Let’s use useParams to render the correct animal
// Creatures.js
import './Creatures.css';
import { getCreaturesData } from '../../data/animalData';
import { useParams } from 'react-router-dom';
const Creatures = () => {
const creatureType = useParams().animal;
const creatureImages = getCreaturesData(creatureType).map(creature => {
const { id, image } = creature;
return <img src={image} key={id} id={id} className="app-img"/>
});
return (
<>
<h1>{creatureType}!</h1>
{creatureImages}
</>
)
}
export default Creatures;
11. Notice that Creatures isn’t using props anymore. Let’s remove those from App.js:
// App.js
import './App.css';
import { Routes, Route, NavLink } from 'react-router-dom';
import Home from '../Home/Home';
import Creatures from '../Creatures/Creatures';
function App() {
return (
<main className="App">
<nav>
<NavLink to="/puppies" className="nav">Puppies</NavLink>
<NavLink to="/sharks" className="nav">Sharks</NavLink>
</nav>
<h1>Puppies or Sharks?</h1>
<Routes>
<Route path="/" element={<Home />}/>
<Route path="/:animal" element={<Creatures />} />
</Routes>
</main>
);
}
export default App;
Rendering the CreatureDetails component
12. Let’s make the images link to our new URL
// Creatures.js
import './Creatures.css';
import { getCreaturesData } from '../../data/animalData';
import { useParams, Link } from 'react-router-dom';
const Creatures = () => {
const creatureType = useParams().animal;
const creatureImages = getCreaturesData(creatureType).map(creature => {
const { id, image } = creature;
return (
<Link to={`/${creatureType}/${id}`}>
<img src={image} key={id} id={id} className="app-img"/>
</Link>
)
});
return (
<>
<h1>{creatureType}!</h1>
{creatureImages}
</>
)
}
export default Creatures;
Let’s explore
- What happens when you click on a shark or puppy image? Does the URL change? What appears on the page?
- Why did we use a
<Link />
and not a<NavLink />
?
13. Let’s tell Router what to do with this new path
// App.js
import './App.css';
import { Routes, Route, NavLink } from 'react-router-dom';
import Home from '../Home/Home';
import Creatures from '../Creatures/Creatures';
import CreatureDetails from '../CreatureDetails/CreatureDetails';
function App() {
return (
<main className="App">
<nav>
<NavLink to="/puppies" className="nav">Puppies</NavLink>
<NavLink to="/sharks" className="nav">Sharks</NavLink>
</nav>
<h1>Puppies or Sharks?</h1>
<Routes>
<Route path="/" element={<Home />}/>
<Route path="/:animal" element={<Creatures />}>
<Route path=":id" element={<CreatureDetails />}/>
</Route>
</Routes>
</main>
);
}
export default App;
Let’s explore
- Why did we nest the routes?
- Why don’t I have a ‘/’ in front of “:id”? (Note: You can include the ‘/’, if you also include the rest of the path. Meaning, you could do
:/id
OR/:animal/:id
) - Is it working? Is CreatureDetails rendering to the page?
14. One more step to getting CreatureDetails to appear
// Creatures.js
import './Creatures.css';
import { getCreaturesData } from '../../data/animalData';
import { useParams, Link, Outlet } from 'react-router-dom';
const Creatures = () => {
const creatureType = useParams().animal;
const creatureImages = getCreaturesData(creatureType).map(creature => {
const { id, image } = creature;
return (
<Link to={`/${creatureType}/${id}`}>
<img src={image} key={id} id={id} className="app-img"/>
</Link>
)
});
return (
<>
<h1>{creatureType}!</h1>
<Outlet />
{creatureImages}
</>
)
}
export default Creatures;
Let’s explore
- Try moving the
<Outlet />
component around. Does it’s placement matter? - Why is the
<Creatures />
component still showing? - What would you have to change if you didn’t want the
<Creatures />
component to render at this path?
Why Use the Outlet
?
The Outlet component serves as a placeholder for rendering nested route content within a parent component. It’s a fundamental tool that allows you to seamlessly integrate child components’ content into a parent’s layout while maintaining UI coherence.
Real-World Analogy: Twitter Inbox
Imagine a scenario similar to the Twitter inbox. When you access your inbox, you see a list of message previews on the left side of the screen, but the right side remains blank until you select a specific conversation. This structure is where the Outlet comes into play.
15. Now let’s make CreatureDetails show the animal’s info (hint: look at animalData.js!)
// CreatureDetails.js
import './CreatureDetails.css';
import { useParams } from 'react-router-dom';
import { getCreatureDetails } from '../../data/animalData';
const CreatureDetails = () => {
const creatureType = useParams().animal;
const creatureId = useParams().id;
const creatureStats = getCreatureDetails(creatureType, creatureId);
return (
<div>
<h1>{creatureStats.name}</h1>
<img src={creatureStats.image} className='app-img-no-hover'/>
<p className='creature-bio'>{creatureStats.bio}</p>
</div>
)
}
export default CreatureDetails;
Now that you know how to use Outlet, let’s explore other ways we can achieve dynamic routing without it.
We are going to remove our nested route and define a new separate route for /:animal/:id
which is not directly nested within the first route /
. Each route has it’s own component.
You would use this approach when you have distinct pages or components that should be displayed separately based on the URL.
import './App.css';
import './App.css';
import { Routes, Route, NavLink } from 'react-router-dom';
import Home from '../Home/Home';
import Creatures from '../Creatures/Creatures';
import CreatureDetails from '../CreatureDetails/CreatureDetails';
function App() {
return (
<main className="App">
<nav>
<NavLink to="/puppies" className="nav">Puppies</NavLink>
<NavLink to="/sharks" className="nav">Sharks</NavLink>
</nav>
<h1>Puppies or Sharks?</h1>
<Routes>
<Route path="/" element={<Home />} />
<Route path=":animal" element={<Creatures />} />
<Route path="/:animal/:id" element={<CreatureDetails />} />
</Routes>
</main>
);
}
export default App;
Now it’s time to remove Outlet
from our Creature.js
file since we don’t have nested routes and we don’t need Outlet
anymore
Final Reflections
- Why use Router?
- Describe the following:
Route
Routes
Link
NavLink
Outlet
useParams
Extra Resources:
- React Router v6 Docs
- React Router v6 Tutorial
- It is likely that you’ll run into use cases for
Navigate
/useNavigate
, which helps you force a URL change. This is a helpful resource!