Home > Back-end >  How to enforce presence of searchParams on react-router-v6 route?
How to enforce presence of searchParams on react-router-v6 route?

Time:04-19

The what?

I am making build a React Typescript app that makes calls to https://swapi.dev to render pages of various resources like people, films or planets.

The why?

I need to make sure that the user is not able to manipulate the URL such that they are able to go to /characters without specifying a pageNumber param like so: localhost:3000/character?page=1

The how?

As an example I have a route like the one below, where I need to Redirect the user to /characters?page=1 if they enter enter /characters

<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>
What have you tried?

I've tried replacing the path to path=characters?page=:id and setting it to exact, but this is not valid syntax in react-router. Also had a quick glance at https://reactrouter.com/docs/en/v6/ , but I couldn't find anything relevant.

How can I help ?

I need help figuring out how to enforce searchParams on a react-router route.

CodePudding user response:

You can't do this at the route level since queryString parameters are not part of the path. Only the path is used by react-router-dom Route components.

Use the useSearchParams hook in the matched/routed component and check for the page queryString parameter and issue an imperative back navigation if it is missing.

Example:

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
  ...

Update

It seems like your question was more about providing a fallback page queryString value than preventing access to routed content if page value was missing. You can use the same code/logic as above, but instead of using navigate to issue a back navigation off the route, just update the 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
  ...

CodePudding user response:

So, I ended up solving this by creating an intermediary component called <ValidateQueryParams/>, here's the code I used for those who are interested:

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>
  • Related