You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Next.js中完善商品搜索功能?现有组件代码求助

实现URL搜索参数过滤商品功能的步骤

1. 在页面组件中获取URL查询参数

在Next.js App Router中,使用useSearchParams钩子获取URL中的search参数,然后将参数传递给ProductCard组件。以首页page.tsx为例:

// app/page.tsx
"use client";
import { useSearchParams } from "next/navigation";
import ProductCard from "@/components/ProductCard";
import SearchInput from "@/components/SearchInput";
// 替换为你从Strapi获取商品数据的逻辑
import { getProducts } from "@/lib/api";

export default async function Home() {
  const products = await getProducts();
  const searchParams = useSearchParams();
  const searchQuery = searchParams.get("search") || "";

  return (
    <div>
      <SearchInput />
      <ProductCard products={products} searchQuery={searchQuery} />
    </div>
  );
}

2. 同步SearchInput与URL参数状态

修改SearchInput组件,从URL参数初始化输入框值,并监听参数变化同步状态,同时修正按钮逻辑避免误提交:

"use client";

import { Search } from "lucide-react";
import { useState, useEffect } from "react";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { useDebounce } from "use-debounce";

const SearchInput = () => {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const [isSearchVisible, setIsSearchVisible] = useState(false);
  // 从URL参数初始化搜索框值
  const [searchQuery, setSearchQuery] = useState(searchParams.get("search") || "");
  const [query] = useDebounce(searchQuery, 500);

  const onSearch = (e: React.FormEvent) => {
    e.preventDefault();

    if (query.trim()) {
      router.push(`/?search=${encodeURIComponent(query)}`);
    } else {
      router.push("/");
    }
  };

  // 监听URL参数变化,同步到搜索框
  useEffect(() => {
    const currentSearch = searchParams.get("search") || "";
    if (currentSearch !== searchQuery) {
      setSearchQuery(currentSearch);
    }
  }, [searchParams, searchQuery]);

  return (
    <form onSubmit={onSearch}>
      <div className="mr-4 relative lg:flex hidden items-center text-gray-400 focus-within:text-gray-600">
        <button
          onClick={() => setIsSearchVisible(!isSearchVisible)}
          className="absolute w-6 h-6 ml-4 text-gray-400 hover:text-gray-600"
          type="button" {/* 避免触发表单提交 */}
        >
          <Search className="w-6 h-6" />
        </button>

        {(!isSearchVisible || pathname !== "/") && (
          <input
            type="text"
            placeholder="Search..."
            className="py-2 pl-12 px-4 rounded-full border border-indigo-200 hover:outline focus:border-blue-500"
            value={searchQuery}
            onChange={(e) => setSearchQuery(e.target.value)}
          />
        )}
      </div>
    </form>
  );
};

export default SearchInput;

3. 优化ProductCard的过滤与无限滚动逻辑

调整ProductCard组件,让无限滚动基于过滤后的商品数组,添加搜索结果提示,并在搜索词变化时重置可见商品数量:

"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import Head from "next/head";
import Image from "next/image";

import InfiniteScroll from "react-infinite-scroll-component";
import loadingimage from "../assets/loading.png";

type Props = {
  products: any[];
  searchQuery: string;
};

const myLoader = ({ src }: { src: string }) => {
  return `http://localhost:1337${src}`;
};

const ProductCard: React.FC<Props> = ({ products, searchQuery }) => {
  const initialProductCount = 15;
  const [visibleProducts, setVisibleProducts] = useState(initialProductCount);
  const [loading, setLoading] = useState(false);
  const productArray = Object.values(products).reverse();
  const filteredProductArray = productArray.filter((product: any) =>
    product.attributes.title.toLowerCase().includes(searchQuery.toLowerCase())
  );

  const loadMoreProducts = () => {
    if (!loading && visibleProducts < filteredProductArray.length) {
      setLoading(true);
      setTimeout(() => {
        const additionalProducts = 15;
        setVisibleProducts((prevCount) => 
          Math.min(prevCount + additionalProducts, filteredProductArray.length)
        );
        setLoading(false);
      }, 1000);
    }
  };

  // 搜索词变化时重置可见商品数量
  useEffect(() => {
    setVisibleProducts(initialProductCount);
  }, [searchQuery]);

  const handleScroll = () => {
    if (
      window.innerHeight + window.scrollY >=
      document.documentElement.scrollHeight - 200
    ) {
      loadMoreProducts();
    }
  };

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, [visibleProducts, filteredProductArray.length]);

  return (
    <div className="bg-white">
      <main className="mx-auto max-w-screen-lg px-6 py-20">
        {/* 搜索结果提示 */}
        {searchQuery && (
          <p className="mb-6 text-gray-700">
            搜索结果:找到 {filteredProductArray.length} 件商品
          </p>
        )}
        {/* 无结果提示 */}
        {filteredProductArray.length === 0 && searchQuery && (
          <div className="text-center py-12">
            <p className="text-gray-500">没有找到匹配的商品</p>
          </div>
        )}
        <InfiniteScroll
          dataLength={visibleProducts}
          next={loadMoreProducts}
          hasMore={visibleProducts < filteredProductArray.length}
          loader={<p></p>}
          endMessage={<p></p>}
        >
          <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-custom lg:grid-cols-3 gap-4 sm:gap-6">
            {filteredProductArray.slice(0, visibleProducts).map((product) => (
              <div
                key={product.id}
                className="border-1 shadow-md rounded-lg bg-white overflow-hidden max-w-[350px]  mx-auto"
              >
                <Head>
                  <title>{product?.attributes?.title}</title>
                  <meta
                    name="description"
                    content={product?.attributes?.description}
                  />
                  <meta
                    name="keywords"
                    content={product?.attributes?.keywords}
                  />
                </Head>
                <div className="object-cover">
                  <Link
                    href={product?.attributes?.link}
                    target="_blank"
                    className="object-cover"
                  >
                    <Image
                      className="rounded-t-lg w-full h-72"
                      loader={myLoader}
                      src={product?.attributes?.image?.data?.attributes?.url}
                      alt={product?.attributes?.title}
                      width={330}
                      height={275}
                    />
                  </Link>
                </div>
                <div className="p-3 text-left flex-grow">
                  <h3 className="font-semibold text-xl pb-3 text-gray-500">
                    {product?.attributes?.title}
                  </h3>
                  <p
                    className={`text-sm text-gray-700${
                      product?.attributes?.description?.length > 100
                        ? " line-clamp-5 text-gray-700"
                        : ""
                    }`}
                  >
                    {product?.attributes?.description}
                  </p>
                </div>
                <div className="p-4 flex justify-between items-end">
                  <p className="text-lg font-bold">
                    &#8377;{product?.attributes?.price}
                  </p>
                  <Link href={product?.attributes?.link} target="_blank">
                    <button className="bg-blue-500 text-white px-4 py-2 rounded-lg">
                      Shop Now
                    </button>
                  </Link>
                </div>
              </div>
            ))}
          </div>
        </InfiniteScroll>
        {loading && (
          <div className="flex justify-center items-center">
            <Image
              src={loadingimage}
              alt="Loading image"
              width={50}
              height={50}
            />
          </div>
        )}
      </main>
    </div>
  );
};

export default ProductCard;

可选:启用防抖自动搜索

如果需要用户输入时自动触发搜索(无需点击提交按钮),可以启用SearchInput中注释的useEffect:

useEffect(() => {
  if (query.trim()) {
    router.push(`/?search=${encodeURIComponent(query)}`);
  } else if (pathname === "/") {
    router.push("/");
  }
}, [query, router, pathname]);

内容的提问来源于stack exchange,提问作者user22492436

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 12:25:54