React Router: Updating from v5

Lada496
5 min readDec 5, 2021

--

This is a memo when I updated React Router in my app from v5 to v6.
Demo app is here.

Photo by Pankaj Patel on Unsplash

Switch -> Routes

Use Routes instead of Switch and exact keyword is not necessary in v6. Also, element is a new attribute to assign components inside of a Route page.

v5

<Switch>
<Route path="/" exact>
<LaunchPage />
</Route>
<Layout>
<Switch>
<Route path="/search-book">
<Home />
</Route>
<Route path="/my-books">
<MyBooks />
</Route>
</Switch>
</Layout>
</Switch>

v6

A minor finding is that it is impossible to put Layout component inside of Routes. As a result, it is necessary to wrap each page component in Layout, respectively. In addition, “*” is important to let Router know the path has at least one child path.

<Routes>
<Route path="/" element={<LaunchPage />} />
<Route path="search-book/*" element={<Layout><Home /></Layout>} />
<Route path="my-books" element={<Layout><MyBooks /></Layout>}/></Routes>

refactoring
I added WithLayout component and wrapped page components in it to avoid the repeating pattern in Routes.

WithLayout

import Layout from "../Layout/Layout.jsx";function WithLayout(Child) {
return function WithLayout(props) {
return (
<Layout>
<Child {...props} />
</Layout>
);
};
}
export default WithLayout;

Home

import SearchBooks from "../components/SearchBooks";
import WithLayout from "../hoc/WithLayout";
const Home = () => {
return <SearchBooks />;
};
export default WithLayout(Home);

MyBooks

import { Fragment, useContext } from "react";
import MyBookItem from "../components/MyBookItem";
import SortMyBooks from "../components/SortMyBooks";
import MyBooksContext from "../store/my-books-context";
import classes from "./MyBooks.module.css";
import WithLayout from "../hoc/WithLayout";
// functionsconst MyBooks = () => {
// functions
return (
<Fragment>
<SortMyBooks
onTitle={sortByTitleHandler}
onDate={sortByDateHandler}
onRating={sortByRatingHandler}
onDefault={defaultHandler}
/>
<div className={classes.box}>
{myBooksCtx.myBooks.length !== 0 && content}
{myBooksCtx.myBooks.length === 0 && (
<p>No books here yet. Let's add your book!</p>
)}
</div>
</Fragment>
);
};
export default WithLayout(MyBooks);

NotFound

import React from "react";
import { Link } from "react-router-dom";
import classes from "./NotFound.module.css";
import WithLayout from "../hoc/WithLayout";
function NotFound() {
return (
<div className={classes["not-found"]}>
<h1>404</h1>
<h2>Page Not Found </h2>
<p>
Back to <Link to="/search-book">Home</Link>
</p>
</div>
);
}
export default WithLayout(NotFound);

App

import React, { Fragment, Suspense } from "react";
import { Route, Routes } from "react-router-dom";
import LaunchPage from "./pages/LaunchPage";
import Home from "./pages/Home";
import LoadingSpinner from "./UI/LoadingSpinner";
import NotFound from "./pages/NotFound";
const MyBooks = React.lazy(() => import("./pages/MyBooks"));
function App() {
return (
<Fragment>
<Suspense
fallback={
<div className="centered">
<LoadingSpinner />
</div>
}
>
<Routes>
<Route path="/" element={<LaunchPage />} />
<Route path="search-book/*" element={<Home />} />
<Route path="my-books" element={<MyBooks />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</Fragment>
);
}
export default App;

Full path -> relative path

Use relative path when you add children paths whereas we had to use full path in v5.

v5

<Route path={match.url} exact>
//elements
</Route>
<Route path={`${match.url}/add-my-books`}>
<AddMyBooks />
</Route>

v6

<Routes>
<Route path="" element={
<BookDetailComponent path={location.pathname} />
}/>
<Route path="add-my-books" element={<AddMyBooks />} />
</Routes>

useRouteMatch -> useLocation

According to the official document, useMatch is the alternative to useRouteMatch. However, useLocation can be an alternative depending on the way of using useRouteMatch. In my case, I used useRouteMatch to know the previous path and this is a case when useLocation becomes the alternative solution.

v5

const BookItem = (props) => {
const itemCtx = useContext(ItemContext);
const match = useRouteMatch();
const updateItemHandler = () => {
itemCtx.updateItem(props.item);
};
return (
<Fragment>
<Link
className={classes.box}
to={`${match.path}/${props.item.id}`}
onClick={updateItemHandler}
>
<img
className={classes.image}
src={props.item.image ? props.item.image : noImage}
alt={props.item.title}
></img>
</Link>
</Fragment>
);
};

v6

const BookItem = (props) => {
const itemCtx = useContext(ItemContext);
const location = useLocation();
const updateItemHandler = () => {
itemCtx.updateItem(props.item);
};
return (
<Fragment>
<Link
className={classes.box}
to={`${location.pathname}/${props.item.id}`}
onClick={updateItemHandler}
>
<img
className={classes.image}
src={props.item.image ? props.item.image : noImage}
alt={props.item.title}
></img>
</Link>
</Fragment>
);
};

useHistory -> useNavigate

useHistory is no longer available and use useNavigate instead when you want to navigate from the previous page to another page.

v5

const history = useHistory();
const submitHandler = (event) => {
event.preventDefault();
const formattedDate = formatDate(inputDate);
const mybook = {
id: "" + inputDate.getTime() + itemCtx.item.id,
date: formattedDate,
rating,
title: itemCtx.item.title,
authors: itemCtx.item.authors,
image: itemCtx.item.image,
comment,
};
myBooksCtx.updateMyBooks(mybook);
history.replace("/my-books");
};

v6

Put an object to specify when you want to replace.

const navigate = useNavigate();
const submitHandler = (event) => {
event.preventDefault();
const formattedDate = formatDate(inputDate);
const mybook = {
id: "" + inputDate.getTime() + itemCtx.item.id,
date: formattedDate,
rating,
title: itemCtx.item.title,
authors: itemCtx.item.authors,
image: itemCtx.item.image,
comment,
};
myBooksCtx.updateMyBooks(mybook);
navigate("/my-books", { replace: true });
};

activeClassName -> isActive

activeClassName was deleted and we have to recognize if a certain nav is active or not by using isActive instead.

v5

const MainNavigation = () => {
return (
<header className={classes.header}>
<div className={classes.logo}>Your Library</div>
<nav className={classes.nav}>
<ul>
<li>
<NavLink to="/search-book" exact activeClassName={classes.active}>
Home
</NavLink>
</li>
<li>
<NavLink to="/my-books" activeClassName={classes.active}>
My Books
</NavLink>
</li>
<li>
<NavLink to="/" exact activeClassName={classes.active}>
Logout
</NavLink>
</li>
</ul>
</nav>
</header>
);
}

v6

const MainNavigation = () => {
return (
<header className={classes.header}>
<div className={classes.logo}>Your Library</div>
<nav className={classes.nav}>
<ul>
<li>
<NavLink
to="/search-book"
className={({ isActive }) => (isActive ? classes.active : null)}
>
Home
</NavLink>
</li>
<li>
<NavLink
to="/my-books"
className={({ isActive }) => (isActive ? classes.active : null)}
>
My Books
</NavLink>
</li>
<li>
<NavLink
to="/"
className={({ isActive }) => (isActive ? classes.active : null)}
>
Logout
</NavLink>
</li>
</ul>
</nav>
</header>
);
};

Redirect -> Navigate

Although I did not use Redirect, I think the deletion of Redirect is another big change from v5. Alternative Component is Navigate but attributes are bit changed. In Redirect, replace was the default and we used push if necessary. On the other hand, in Navigate, push is the default and we need to use replace.

v5

<Redirect to="contactme" />
<Redirect to="home" push />

v6

<Navigate to="contactme" replace />
<Navigate to="home" />

You can find more detailed information from the official document.

My impression of this update is that it becomes easier to deal with nested paths in v6. Also, it gets more important to separate page components and other components because of using element, which I believe makes my coding cleaner. However, the deletion of activeClassName is a bit inconvenient for me.

Thank you for reading :)

--

--