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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 04:45:14