ウェブサイト検索

reactjs - React/TypeScript 条件付きルーティング - React Router Dom v5 から v6 への更新


私はTypeScriptの初心者で、アプリのReact Routerのバージョンをv5からv6に更新しています。この条件付きルーティングのラッパーをどのように更新するかでつまずいています:

// v5
import React from "react";
import {
  Redirect,
  RedirectProps,
  Route,
  RouteComponentProps,
  RouteProps,
} from "react-router-dom";

export type ConditionalRouteProps = React.FC<
  RouteProps &
    Partial<RedirectProps> & {
      condition?: boolean;
    }
>;

const ConditionalRoute: ConditionalRouteProps = ({
  component: Component,
  condition = false,
  children,
  ...rest
}) => {
  const toRender = (props: RouteComponentProps): React.ReactElement | null => {
    if (!condition) return <Redirect to={REDIRECT_DESTINATION} />;
    if (Component) return <Component {...props} />;
    return <>{children}</>;
  };

  return <Route {...rest} render={toRender} />;
};

export { ConditionalRoute };

レンダーメソッドとRedirectコンポーネントが廃止されたことは知っています。これまでに私が行ったことは次の通りです(作業中):

//v6
import React from "react";
import { Navigate, NavigateProps, Route, RouteProps } from "react-router-dom";

export type ConditionalRouteProps = React.FC<
  RouteProps &
    Partial<NavigateProps> & {
      condition?: boolean;
    }
>;

const ConditionalRoute: ConditionalRouteProps = ({
  element: Element,
  condition = false,
  children,
  ...rest
}) => {
  return (

<Route {...rest}>
  {!condition ? (
    <Navigate to={REDIRECT_DESTINATION} />
  ) : Element ? (
    <Element />
  ) : (
    <>{children}</>
  )}
</Route>
  );
};

export { ConditionalRoute };

そして、<Element /> で私が得ているエラーは次の通りです:JSX 要素型 'Element' にはコンストラクトまたは呼び出しのシグネチャが存在しません。ts(2604)

これはラッパーが呼び出されている方法です:

const AdminRoute: ConditionalRouteProps = (props) => {
  const { isAdmin } = useContext(UserContext);
  return <ConditionalRoute condition={isAdmin} {...props} />;
};

...

const TestPage: React.FC = () => {
  return (
    <>
      <Link to="/admin" data-testid="navigateToAdmin">
        Navigate to admin
      </Link>
    </>
  );
};

const TestRoutes: React.FC = () => {
  return (
    <Routes>
      <Route
        path="/admin"
        element={<AdminRoute element={() => <h1>Admin Page</h1>} />}
      />
      <Route path="/" element={<TestPage />} />
    </Routes>
  );
};

describe("<AdminRoute />", () => {
  it("should let admin users in", () => {
    const { history } = renderWithProviders(<TestRoutes />, {
      providerProps: { userProviderProps: { isAdmin: true } },
    });

  userEvent.click(screen.getByTestId("navigateToAdmin"));
  expect(history.location.pathname).toEqual("/admin");
  });

  it("should redirect everyone else", () => {
    const { history } = renderWithProviders(<TestRoutes />);

    userEvent.click(screen.getByTestId("navigateToAdmin"));

    expect(screen.queryByText("Admin Page")).not.toBeInTheDocument();
    expect(history.location.pathname).toEqual("/");
  });
});

回答 #1

私はあなたがルート保護を少し複雑にしていると思います。react-router-dom@6 のルートレンダリングはv6ではかなり異なります。私たちは自分のコンポーネント内で直接 Route をレンダリングするのではなく、Routes コンポーネントにレンダリングするか、他の Route コンポーネントの下に直接ネストしてルートネストを行う必要があります。

私は次の実装を提案します。

//v6
import React from "react";
import { Navigate, Outlet } from "react-router-dom";

interface IConditionalRoute = {
  condition: boolean;
};

const ConditionalRoute = ({ condition = false }: IConditionalRoute) => {
  return condition ? <Outlet /> : <Navigate to={REDIRECT_DESTINATION} />;
};

export { ConditionalRoute };

注意:私はTypeScriptにあまり強くないので、構文が完全に正しいとは限りませんが、このインターフェース/コードは必要なものに近づけるはずです。

使用法:

AdminRoute は、レイアウトルートと呼ばれるルートでレンダリングされます。このルートは Outlet をレンダリングし、他の Route コンポーネントをラップします。入れ子になったルートのパスがマッチング/レンダリングに使用されるように、path プロパティは省略してください。

const AdminRoute: ConditionalRouteProps = () => {
  const { isAdmin } = useContext(UserContext);
  return <ConditionalRoute condition={isAdmin} />;
};

const TestRoutes: React.FC = () => {
  return (
    <Routes>
      <Route element={<AdminRoute />}>
        <Route path="/admin" element={<h1>Admin Page</h1>} />
      </Route>
      <Route path="/" element={<TestPage />} />
    </Routes>
  );
};