Routing
Overview
- Similar in function to a server-side router in an MVC framework
- Associates a route (url) with a particular controller action
- React Router switches between (page/container) components when a route changes
- Back button is broken by default when page/container components change
- the browser's history is not updated with a new url when page/container components change
- React Router programmatically adds entries to the browser's history
- enables the back button to work in React applications
There are two versions:
- BrowserRouter (react-router-dom) for web applications.
- NativeRouter (react-router-native) for use with React Native.
Installation
Install the package
npm install react-router-dom@6.3
Add the script tag
index.html
...
<script src="/node_modules/react/umd/react.development.js"></script>
<script src="/node_modules/react-dom/umd/react-dom.development.js"></script>
+ <script src="/node_modules/history/umd/history.development.js"></script>
+ <script src="/node_modules/react-router/umd/react-router.development.js"></script>
+ <script src="/node_modules/react-router-dom/umd/react-router-dom.development.js"></script>
<script src="/node_modules/@babel/standalone/babel.min.js"></script>
<script src="/node_modules/axios/dist/axios.min.js"></script>
<script type="text/babel" src="/main.js"></script>! Be sure that the
main.js
script tag's src attribute starts with a/
or the router will not work properly when you refresh the page.Log the
RouterRouterDOM
to verify it is installed properlymain.js
console.log(window.ReactRouterDOM);
In the console you should see:
{BrowserRouter: ƒ, HashRouter: ƒ, Link: ƒ, …}
Basics
Add these styles
/* styles.css */
.container {
border: 1px solid #ddd;
margin: 30px;
padding: 30px;
}
nav ul {
list-style: none;
}
nav ul li {
display: inline;
}
nav ul li:after {
content: ' | ';
}
nav ul li:last-child:after {
content: '';
}Try this code
const {
BrowserRouter: Router,
Route,
Routes,
Link,
NavLink,
Navigate,
useParams,
useLocation,
useNavigation,
} = window.ReactRouterDOM;
function Home() {
return <h2>Home</h2>;
}
function About() {
return <h2>About</h2>;
}
function Contact() {
return <h2>Contact</h2>;
}
function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</nav>
<div className="container">
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
</Routes>
</div>
</div>
</Router>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);Change the Link tags to NavLink tags
<nav>
<ul>
<li>
<NavLink to="/">Home</NavLink>
</li>
<li>
<NavLink to="/about">About</NavLink>
</li>
<li>
<NavLink to="/contact">Contact</NavLink>
</li>
</ul>
</nav>Add the following style
styles.css
... .active {
background-color: #bee5eb;
padding: 10px;
}Refresh the browser and see the navigation items are highlighted.
Not Found (404)
Be sure you are running a development web server like serve with the
-s
flag.package.json
"scripts": {
"start": "serve -s",
...
},
...Change the URL to
http://localhost:5000/noroute
The navigation renders but the page is blank. Ideally, we would like to show a
NotFound
component when this happens.
3) Create a NotFound
component
function NotFound() {
return (
<>
<h2>Uh oh.</h2>
<p>
The page you requested could not be found. Is there any chance you
were looking for one of these?
</p>
</>
);
}
Add a route for it with no path
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
+ <Route path="*" element={<NotFound />} />
</Routes>Navigate to the home route and then the contact route and notice the
NotFound
component shows when visiting every routeNavigate to the various routes again and notice that only when you manually go to a route that doesn't exist like:
/noroute
theNotFound
component renders.
URL Parameters
This example builds on the code from the previous demonstrations in this section.
Create a
Movie
model class.- Add it just before the App component
class Movie {
constructor(id, name, description, type) {
this.id = id;
this.name = name;
this.description = description;
this.type = type;
}
}Create an array of mock movies
const movies = [
new Movie(
1,
' Titanic',
'A seventeen-year-old aristocrat falls in love with a kind but poor artist aboard the luxurious, ill-fated R.M.S. Titanic.',
'Drama'
),
new Movie(
2,
' E.T. the Extra-Terrestrial',
'A troubled child summons the courage to help a friendly alien escape Earth and return to his home world.',
'Fantasy'
),
new Movie(
3,
'The Wizard of Oz',
// tslint:disable-next-line:max-line-length
'Dorothy Gale is swept away from a farm in Kansas to a magical land of Oz in a tornado and embarks on a quest with her new friends to see the Wizard who can help her return home in Kansas and help her friends as well.',
'Fantasy'
),
new Movie(
4,
'Star Wars: Episode IV - A New Hope ',
// tslint:disable-next-line:max-line-length
'Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a Wookiee and two droids to save the galaxy from the Empire/`s world-destroying battle-station while also attempting to rescue Princess Leia from the evil Darth Vader.',
'Action'
),
];Create a
Movies
component to list moviesfunction MovieList({ movies }) {
return (
<div>
<h2>Movies</h2>
<ul>
{movies.map((movie) => (
<li key={movie.id}>
<Link to={`./${movie.id}`}>{movie.name}</Link>
</li>
))}
</ul>
</div>
);
}Add a Route to go to the
Movies
componentNotice how we pass props to a the
Movies
component which is rendered by the React Router<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
+ <Route path="movies" element={<MovieList movies={movies} />} />
<Route path="movies/:id" element={<MovieDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>Add a NavLink to navigate to the
Movies
component.<nav>
<ul>
<li>
<NavLink to="/">Home</NavLink>
</li>
<li>
<NavLink to="/about">About</NavLink>
</li>
<li>
<NavLink to="/contact">Contact</NavLink>
</li>
+ <li>
+ <NavLink to="/movies">Movies</NavLink>
+ </li>
</ul>
</nav>Create a
MovieDetail
component to show the detail about a particular movie.function MovieDetail() {
let { id } = useParams();
id = Number(id);
const movie = movies.find((movie) => movie.id === id);
return (
<div>
<h3>{movie.name}</h3>
<p>{movie.description}</p>
</div>
);
}Add a Route to go to the
MovieDetail
component....
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="movies" element={<MovieList movies={movies} />} />
+ <Route path="movies/:id" element={<MovieDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>Test the application and verify you can now click on a movie in the list and the movie detail route and component loads. Verify the browser's back button still works to return to the list after visiting the detail component.
Nesting
Edit the
MovieList
component to nest theMovieDetail
component route inside itself.function MovieList({ movies }) {
return (
<div>
<h2>Movies</h2>
<ul>
{movies.map((movie) => (
<li key={movie.id}>
<Link to={`./${movie.id}`}>{movie.name}</Link>
</li>
))}
</ul>
+ <div style={{ marginLeft: "40px" }}>
+ <Routes>
+ <Route path=":id" element={<MovieDetail />} />
+ </Routes>
+ </div>
</div>
);
}Remove the
MovieDetail
route from theApp
component. Add an astericks (*) after themovies
route so it can find the nested routes....
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
+ <Route path="movies/*" element={<MovieList movies={movies} />} />
- <Route path="movies/:id" element={<MovieDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>Refresh the browser and notice that the movie detail now shows below the movie list after clicking a movie link.