DEV Community

Cover image for React Router: Updating from v5
Yuko
Yuko

Posted on • Updated on

React Router: Updating from v5

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

**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;
Enter fullscreen mode Exit fullscreen mode

Home

    import SearchBooks from "../components/SearchBooks";
    import WithLayout from "../hoc/WithLayout";

    const Home = () => {
      return <SearchBooks />;
    };
    export default **WithLayout(Home)**;
Enter fullscreen mode Exit fullscreen mode

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)**;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

v6

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

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>
      );
    };
Enter fullscreen mode Exit fullscreen mode

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>
      );
    };
Enter fullscreen mode Exit fullscreen mode

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");
      };
Enter fullscreen mode Exit fullscreen mode

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 });
      };
Enter fullscreen mode Exit fullscreen mode

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>
      );
    }
Enter fullscreen mode Exit fullscreen mode

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>
      );
    };
Enter fullscreen mode Exit fullscreen mode

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 />
Enter fullscreen mode Exit fullscreen mode

v6

    <Navigate to="contactme" replace />
    <Navigate to="home" />
Enter fullscreen mode Exit fullscreen mode

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

Oldest comments (0)