如何在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"> ₹{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
相关产品推荐
相关产品推荐

