页面刷新react-router-dom的问题

问题描述 投票:0回答:1

我正在编写一个在线商店,它有一个目录和一个用于查询输入的输入。通过单击 Enter(在除目录之外的任何页面上),我们可以进入目录本身,并且查询将进入搜索参数。

问题的本质:如果我在目录中并在输入中输入新的查询,则网址会更新,但目录看不到更改,因此“看不到”查询已更新。你能告诉我该怎么做吗?

我的代码:

标头.tsx

import Logo from '@/components/shared/Logo/Logo';
import Quantity from '@/components/shared/Quantity/Quantity';
import { toast } from '@/components/ui/use-toast';
import { PagePaths } from '@/enum/PagePaths';
import { useActions } from '@/hooks/general/useActions';
import { useTypedSelector } from '@/hooks/general/useTypedSelector';
import { getBasketQuantitySelector } from '@/store/slices/basket/basket.selectors';
import { getFavouriteQuantitySelector } from '@/store/slices/favourites/favourites.selectors';
import { getUserInfoSelector } from '@/store/slices/user/user.selectors';
import { Link, useNavigate } from 'react-router-dom';
import classes from './Header.module.scss';
import SearchInput from "./components/SearchInput";

function Header() {
  const { isAuth, isAdmin } = useTypedSelector(getUserInfoSelector);
  const { logout } = useActions();
  const favouritesQuantity = useTypedSelector(getFavouriteQuantitySelector);
  const basketItemsQuantity = useTypedSelector(getBasketQuantitySelector);
  const navigate = useNavigate();

  async function handleButton() {
    if (!isAuth) return navigate(PagePaths.AUTHENTICATION.LOGIN);

    logout();
    toast({ title: `Success!` });
  }

  return (
    <header className={classes.header}>
      <Logo />
      <SearchInput placeholder="Search..." />
      <div className={classes.header__right}>
        <Link
          to="/favourites"
          className={[classes.header__btn, classes.header__favourite].join(' ')}
        >
          <Quantity quantity={favouritesQuantity} />
        </Link>
        <Link
          to={PagePaths.CART}
          className={[classes.header__btn, classes.header__cart].join(' ')}
        >
          <Quantity quantity={basketItemsQuantity} />
        </Link>
        <button onClick={handleButton}>{isAuth ? 'Logout' : 'Sign in'}</button>
        {isAdmin && <Link to={PagePaths.ADMIN.HOME}>Admin</Link>}
      </div>
    </header>
  );
}

export default Header;

SearchInput.tsx

import { PagePaths } from '@/enum/PagePaths';
import { useMySearchParams } from '@/hooks/general/useMySearchParams';
import { InputHTMLAttributes, KeyboardEvent } from 'react';
import { useNavigate } from 'react-router-dom';

interface SearchInputProps extends InputHTMLAttributes<HTMLInputElement> {}

function SearchInput({ placeholder }: SearchInputProps) {
  const navigate = useNavigate();
  const searchTerm = useMySearchParams('searchTerm');

  function handleEnterDown(e: KeyboardEvent<HTMLInputElement>) {
    if (e.key !== 'Enter') return;
    navigate(`${PagePaths.CATALOG}?searchTerm=${e.currentTarget.value}`);
  }

  return (
    <form
      className="max-w-[400px] w-full"
      role="search"
      onSubmit={(e) => e.preventDefault()}
    >
      <input
        className="w-full py-1 px-3 rounded-md"
        type="search"
        placeholder={placeholder}
        defaultValue={searchTerm || ''}
        aria-label="Search"
        onKeyDown={handleEnterDown}
      />
    </form>
  );
}

export default SearchInput;

目录.tsx

import CardsContainer from '@/components/shared/CardsContainer/CardsContainer';
import {
  Sheet,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
  SheetContent as SheetWrapper,
} from '@/components/ui/sheet';
import { useProducts } from '@/hooks/features/useProducts/useProducts';
import { Settings2 as FiltersIcon } from 'lucide-react';
import CatalogPagination from './components/CatalogPagination';
import FiltersLayout from './components/FiltersLayout/FiltersLayout';
import { useFilters } from './useFilters';

function Catalog() {
  const { filters, changeFilters, page, changePage, searchTerm } = useFilters();
  const { data } = useProducts({ filters, page, searchTerm });
  const isPaginationNeeded = data && data.totalPages > 1;
  const isProductsFound = data?.products && data?.products.length !== 0;

  return (
    <section className="rows-container">
      <Sheet>
        <div className="flex gap-5 items-center mb-3">
          <strong className="subtitle">Catalog</strong>
          <SheetTrigger className="link flex items-center gap-2 py-1 px-2 rounded-md">
            <FiltersIcon />
            Show filters
          </SheetTrigger>
        </div>
        <SheetWrapper>
          <SheetHeader className="mb-7">
            <SheetTitle className="flex items-center gap-2">
              <FiltersIcon /> Filters
            </SheetTitle>
          </SheetHeader>
          {/* Sheet Content */}
          <div>
            <FiltersLayout filters={filters} changeFilters={changeFilters} />
          </div>
          {/* Sheet Content END */}
        </SheetWrapper>
      </Sheet>

      {isProductsFound ? (
        <CardsContainer products={data.products} />
      ) : (
        <p>Empty...</p>
      )}

      {isPaginationNeeded && (
        <CatalogPagination
          currentPage={page}
          totalPages={data.totalPages}
          setCurrentPage={changePage}
        />
      )}
    </section>
  );
}

export default Catalog;

useFilters.tsx

import { IProductFitlers } from '@/hooks/features/useProducts/filters.types';
import { ProductQueryData } from '@/services/product/product.types';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { configurateUrlParams, parseParamsFromUrl } from './catalog.helpers';

export const useFilters = () => {
  const [_, setSearchParams] = useSearchParams();
  const [page, setPage] = useState(1);
  const [filters, setFilters] = useState<IProductFitlers>({});
  const [searchTerm, setSearchTerm] = useState('');

  useEffect(() => {
    const params = window.location.search.slice(1);
    if (!params) return;

    const queryData = parseParamsFromUrl<ProductQueryData>(params, {
      tryToParseNumbersInArray: true,
      tryToParseNumbersInObject: true,
      tryToParsePrimitive: true,
    });
    setFilters(queryData?.filters || {});
    setPage(queryData?.page ? +queryData.page : 1);
    setSearchTerm(queryData.searchTerm || '');
  }, []);

  function changeFilters(newFilters: IProductFitlers) {
    const isReset = Object.keys(newFilters).length === 0;
    let objectToServer: ProductQueryData = { page: 1 };

    if (!isReset) {
      objectToServer = {
        ...objectToServer,
        filters: newFilters,
        searchTerm,
      }; 
    } else setSearchTerm('');

    setSearchParams(configurateUrlParams(objectToServer));
    setFilters(newFilters);
    setPage(1);
  }

  function changePage(newPage: number) {
    setSearchParams(
      configurateUrlParams({ filters, page: newPage, searchTerm })
    );
    setPage(newPage);
  }

  return { filters, changeFilters, page, changePage, searchTerm };
};
reactjs react-router-dom
1个回答
0
投票

问题在于您将 URL 搜索参数复制到本地 React 状态中,这有点像 React 反模式,因为您需要保持它们同步。各种组件应该直接读取和更新搜索参数。

请注意,只要您有

useState
|
useEffect
耦合,您就可能会实现这种反模式。直接计算并使用导出的“状态”或使用
useMemo
钩子来记忆并提供稳定的参考值。

搜索输入

更新为使用

useSearchParams
挂钩来读取和设置
"searchTerm"
查询参数。

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

function SearchInput({ placeholder }: SearchInputProps) {
  const [searchParams, setSearchParams] = useSearchParams();
  const searchTerm = searchParams.get('searchTerm');

  function handleEnterDown(e: KeyboardEvent<HTMLInputElement>) {
    if (e.key !== 'Enter') return;

    setSearchParams(searchParams => {
      searchParams.set("searchTerm", e.currentTarget.value);
      return searchParams;
    });
  }

  return (
    <form
      className="max-w-[400px] w-full"
      role="search"
      onSubmit={(e) => e.preventDefault()}
    >
      <input
        className="w-full py-1 px-3 rounded-md"
        type="search"
        placeholder={placeholder}
        defaultValue={searchTerm || ''}
        aria-label="Search"
        onKeyDown={handleEnterDown}
      />
    </form>
  );
}

使用过滤器

更新要读取的钩子,并管理搜索参数,而不是需要保持同步的本地状态。

import { IProductFitlers } from '@/hooks/features/useProducts/filters.types';
import { useSearchParams } from 'react-router-dom';
import { configurateUrlParams } from './catalog.helpers';

export const useFilters = () => {
  const [searchParams, setSearchParams] = useSearchParams();

  const page = Number(searchParams.get("page") || 1);
  const filters = searchParams.get("filters") || {};
  const searchTerm = searchParams.get("searchTerm") || "";

  function changeFilters(newFilters: IProductFitlers) {
    const isReset = Object.keys(newFilters).length === 0;

    if (!isReset) {
      setSearchParams(searchParams => {
        searchParams.set("page", 1);
        // I don't know what `configurateUrlParams` does so I'm just assuming
        // it does some string serialization of the filters array/object
        // to be parameterized for the URL
        searchParams.set(
          "filters",
          configurateUrlParams({ filters: newFilters }).filters
        );
        return searchParams;
      });
    } else {
      setSearchParams(searchParams => {
        searchParams.delete("searchTerm");
        searchParams.set("page", 1);
        return searchParams;
      });
    }
  }

  function changePage(newPage: number) {
    setSearchParams(searchParams => {
      searchParams.set("page", newPage);
      return searchParams;
    });
  }

  return { filters, changeFilters, page, changePage, searchTerm };
};
© www.soinside.com 2019 - 2024. All rights reserved.