NextJS 14服务端组件无法用useState,求实时搜索过滤方案
解决方案
核心思路
服务端组件无法维护客户端交互状态,因此需要把实时搜索+过滤渲染的逻辑封装到客户端组件中,服务端组件只负责一次性获取初始数据并传递给客户端组件。
步骤1:修改搜索输入组件,添加回调传递搜索值
让InputSearch组件能够把输入的搜索内容传递给父组件:
"use client" import { Input } from "@/components/ui/input" import { useState } from 'react'; type InputSearchProps = { onSearchChange: (value: string) => void; } export default function InputSearch({ onSearchChange }: InputSearchProps) { const [search, setSearch] = useState('') const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const value = e.target.value; setSearch(value); onSearchChange(value); } return( <Input placeholder="Buscar categoría..." onChange={handleChange} value={search} /> ) }
步骤2:创建客户端组件,实现过滤与渲染
新建一个客户端组件CategoryAccordion,接收服务端传来的初始分类、子分类数据,内部维护搜索状态并实时过滤:
"use client" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion" import { Button } from "@/components/ui/button" import { useState, useEffect } from 'react'; import InputSearch from "./search-input"; // 路径根据实际项目调整 // 类型定义,根据你的数据库实际字段调整 type Category = { id: string; name: string; } type Subcategory = { id: string; name: string; id_category: string; } type CategoryAccordionProps = { initialCategorias: Category[]; initialSubcategorias: Subcategory[]; } export default function CategoryAccordion({ initialCategorias, initialSubcategorias }: CategoryAccordionProps) { const [searchTerm, setSearchTerm] = useState(''); const [filteredCategorias, setFilteredCategorias] = useState(initialCategorias); const [filteredSubcategorias, setFilteredSubcategorias] = useState(initialSubcategorias); // 监听搜索词变化,实时过滤数据 useEffect(() => { const lowerSearch = searchTerm.toLowerCase(); // 过滤分类:分类名匹配,或包含匹配的子分类 const filteredCats = initialCategorias.filter(cat => { const catNameMatch = cat.name.toLowerCase().includes(lowerSearch); const hasMatchingSubcat = initialSubcategorias.some(sub => sub.id_category === cat.id && sub.name.toLowerCase().includes(lowerSearch) ); return catNameMatch || hasMatchingSubcat; }); // 过滤子分类:仅保留属于过滤后分类且名称匹配的子分类 const filteredSubs = initialSubcategorias.filter(sub => filteredCats.some(cat => cat.id === sub.id_category) && sub.name.toLowerCase().includes(lowerSearch) ); setFilteredCategorias(filteredCats); setFilteredSubcategorias(filteredSubs); }, [searchTerm, initialCategorias, initialSubcategorias]); return ( <> <InputSearch onSearchChange={(value) => setSearchTerm(value)} /> <Accordion type="single" collapsible className="w-full"> {filteredCategorias.map((categoria) => ( <AccordionItem key={categoria.id} value={categoria.name}> <AccordionTrigger>{categoria.name}</AccordionTrigger> <AccordionContent> {filteredSubcategorias .filter(sub => sub.id_category === categoria.id) .map(sub => ( <div key={sub.id}> <Button variant="link">{sub.name}</Button> </div> ))} </AccordionContent> </AccordionItem> ))} </Accordion> </> ) }
步骤3:修改服务端页面组件
在page.tsx中,不再直接渲染InputSearch和原始折叠面板,而是渲染新建的客户端组件,并传递初始数据:
import Navbar from "@/components/navbar"; import { Heading } from "@/components/ui/heading" import { db } from "@/lib/db" import CategoryAccordion from "@/app/table/categorias/components/category-accordion"; // 路径根据实际调整 const SearchCategoryPage = async() => { // 服务端一次性获取所有分类和子分类数据 const categorias = await db.table_Categories.findMany({ orderBy: { name: "asc", } }) const subcategorias = await db.table_Subcategories.findMany({ orderBy: { name: "asc", } }) return( <div> <Navbar /> <Heading title="Categorías" description="Description" /> <div className="pl-4 pr-4 w-[100%]"> <CategoryAccordion initialCategorias={categorias} initialSubcategorias={subcategorias} /> </div> </div> ) } export default SearchCategoryPage
这样修改后,用户每输入一个字符,客户端组件就会实时过滤分类和子分类,更新折叠面板的显示内容,完全符合你想要的实时过滤效果,且不需要使用useSearchParams。
内容的提问来源于stack exchange,提问作者user24647795
相关产品推荐
相关产品推荐

