This is a memo when I updated React Router in my app from v5 to v6.
Demo app is here.
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";
// functions
const 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 :)
The original article is here
Top comments (0)