React Router: v5 からの更新


アプリ内のReact Routerをv5からv6にアップデートした時のメモです.
デモアプリは here です.

スイッチ -> ルート



v6 では Switch の代わりに Routes を使用し、exact キーワードは必要ありません.また、 element は Route ページ内のコンポーネントを割り当てるための新しい属性です.

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



ちょっとした発見として、レイアウト コンポーネントをルート内に配置することは不可能です.そのため、各ページ コンポーネントをそれぞれレイアウトでラップする必要があります.さらに、「*」は、パスに少なくとも 1 つの子パスがあることを Router に知らせるために重要です.

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


**リファクタリング
Routes でのパターンの繰り返しを避けるために、WithLayout コンポーネントとラップされたページ コンポーネントを追加しました.

レイアウトあり

    import Layout from "../Layout/Layout.jsx";

    function WithLayout(Child) {
      return function WithLayout(props) {
        return (
          <Layout>
            <Child {...props} />
          </Layout>
        );
      };
    }

    export default WithLayout;




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

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


私の本

    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)**;


見つかりません

    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);


アプリ

    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;


フルパス -> 相対パス



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



公式ドキュメントによると、useMatch は useRouteMatch の代替です.ただし、useRouteMatch の使用方法によっては、useLocation を代替することができます.私の場合、以前のパスを知るために useRouteMatch を使用しました.これは、useLocation が代替ソリューションになる場合です.

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 は使用できなくなりました.前のページから別のページに移動する場合は、代わりに useNavigate を使用してください.

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



置き換えたいときに指定するオブジェクトを配置します.

    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 が削除されたため、代わりに isActive を使用して、特定のナビゲーションがアクティブかどうかを認識する必要があります.

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 は使っていませんが、Redirect の削除も v5 からの大きな変更点だと思います.代替コンポーネントはナビゲートですが、属性が少し変更されています. Redirect では、replace がデフォルトであり、必要に応じて push を使用しました.一方、Navigate では push がデフォルトであり、replace を使用する必要があります.

v5




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


v6




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


the official document からより詳細な情報を見つけることができます.

このアップデートの私の印象は、v6 でネストされたパスを処理しやすくなったということです.また、要素を使用するため、ページ コンポーネントとその他のコンポーネントを分離することがより重要になります.これにより、コーディングがよりクリーンになると思います.ただし、activeClassName の削除は、私にとっては少し不便です.

読んでくれてありがとう :)

元の記事は here です