如何在 react-router-v6 路由上强制存在 searchParams?

How to enforce presence of searchParams on react-router-v6 route?

什么?

我正在构建一个 React+Typescript 应用程序,它调用 https://swapi.dev 来呈现各种资源的页面,例如 peoplefilmsplanets

为什么?

我需要确保用户无法操纵 URL,这样他们就可以在不指定 pageNumber 参数的情况下转到 /characters,如下所示:localhost:3000/character?page=1

怎么办?

例如,我有一条如下所示的路线,如果他们输入 /characters

,我需要将用户重定向到 /characters?page=1
<Routes>
    <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route
          path="characters"
          element={<SwapiResourcePage resourceType={"people"}
        />
        <Route
          path="characters/:id"
          element={<SwapiResourceDetailsPage resourceType={"people"} 
        />
    </Route>
</Routes>
你试过什么?

我已经尝试将路径替换为 path=characters?page=:id 并将其设置为精确,但这在 react-router 中是无效的语法。还快速浏览了 https://reactrouter.com/docs/en/v6/ ,但我找不到任何相关内容。

我能提供什么帮助?

我需要帮助弄清楚如何在 react-router 路由上强制执行 searchParams。

您不能在路由级别执行此操作,因为 queryString 参数不是路径的一部分。只有路径被 react-router-dom Route 个组件使用。

使用 matched/routed 组件中的 useSearchParams 挂钩并检查 page queryString 参数,如果缺少则发出命令式后退导航。

示例:

import { useNavigate, useSearchParams } from 'react-router-dom';

const SwapiResourcePage = ({ resourceType }) => {
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();
  const page = searchParams.get("page");

  useEffect(() => {
    if (!page) navigate(-1); // <-- no page param, navigate back
  }, [navigate, page]);

  ...

  if (!page) {
    return null;
  }

  // render content
  ...

更新

您的问题似乎更多是关于提供后备 page queryString 值,而不是在缺少 page 值时阻止访问路由内容。您可以使用与上面相同的 code/logic,但不是使用 navigate 发出路线后退导航,只需更新 queryString。

import { useSearchParams } from 'react-router-dom';

const SwapiResourcePage = ({ resourceType }) => {
  const [searchParams, setSearchparams] = useSearchParams();
  const page = searchParams.get("page");

  useEffect(() => {
    if (!page) {
      // No page param, replace current
      searchParams.set("page", 1);
      setSearchparams(searchParams, { replace });
    }
  }, [searchParams, setSearchparams, page]);

  ...

  if (!page) {
    return null;
  }

  // render content
  ...

所以,我最终通过创建一个名为 <ValidateQueryParams/> 的中间组件解决了这个问题,这是我为感兴趣的人使用的代码:

import React, { useEffect } from "react";
import { useSearchParams } from "react-router-dom";
import validatePageNumber from "../helpers/validatePageNumber";
import SwapiResourcePage from "../pages/SwapiResourcePage";

interface ValidateQueryParamProps {
  resourceType: string;
}

const ValidateQueryParam: React.FC<ValidateQueryParamProps> = ({
  resourceType,
}) => {
  const [searchParams, setSearchParams] = useSearchParams();
  const pageNumber = searchParams.get("page");

  useEffect(() => {
    if (
      pageNumber == null ||
      !pageNumber.match(/\d+/) ||
      !validatePageNumber(resourceType, parseInt(pageNumber))
    ) {
      setSearchParams({ page: "1" });
    }
  }, []);
  return <SwapiResourcePage resourceType={resourceType} />;
};

export default ValidateQueryParam;
<Routes>
    <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route
          path="people"
          element={<ValidateQueryParam resourceType={"people"} />}
        />
        <Route
          path="people/:id"
          element={<SwapiResourceDetailsPage resourceType={"people"} />}
        />
    </Route>
</Routes>