Search code examples
reactjsnext.jssearchfrontend

Search filter NextJS 14 can't use useState on server component (page.tsx)


I'm currently learning NextJS and React, and I'm stuck trying to figure out how to make an input search filter to filter a table.

I've seen a lot of videos and comments about using useSearchParams, but it's not the thing I'd like to do.

I'd like to filter in real-time, making the table refresh every time the user presses a key on the input and seeing the number of rows decrease as I write more in the input filter.

What should I do?

This is my search input file:

"use client"

import { Input } from "@/components/ui/input"

import { useState} from 'react';

export default function InputSearch() {
    const [search, setSearch] = useState('')
    return(
        <Input 
            placeholder="Buscar categoría..."
            onChange={(e) => setSearch(e.target.value)}
            value={search}
        />
    )
}

And this is my page.tsx file (server component)

import Navbar from "@/components/navbar";
import {Heading} from "@/components/ui/heading"
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion" 
import {Button} from "@/components/ui/button"
import {db} from "@/lib/db"
import InputSearch from "@/app/table/categorias/components/search-input";

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></Navbar>
      <Heading 
      title="Categorías" 
      description="Description"
      />
      <div className="pl-4 pr-4 w-[100%]">
      <InputSearch/>
      <Accordion type="single" collapsible className="w-full">
      {categorias
      .map((filteredCategoria) => (
        <AccordionItem key={filteredCategoria.id} value={filteredCategoria.name}>
          <AccordionTrigger>{filteredCategoria.name}</AccordionTrigger>
          <AccordionContent>
            {subcategorias
            .filter((subcategoria) => subcategoria.id_category === filteredCategoria.id)
            .map((filteredSubcategoria) => (
                <div>
                  <Button variant="link" key={filteredSubcategoria.id}>{filteredSubcategoria.name}</Button>  
                </div>            
                ))}
          </AccordionContent>
        </AccordionItem>
      ))}
    </Accordion>
    </div>
    </div>
  )
}

export default SearchCategoryPage


Solution

  • There are several ways to implement this approach. One of them is to use state management like useContext or redux to get the user's input search value and pass to your desired component. However, I recommend using URL search parameters, which means converting the user's input query into URL search query. This way, the current page can directly access this variable. This method is also recommended by the Next.js official documentation. You can refer to https://nextjs.org/learn/dashboard-app/adding-search-and-pagination

        // InputSearch
        "use client"
        
        import { Input } from "@/components/ui/input"
        import { useSearchParams } from 'next/navigation'
        
        export default function InputSearch() {
            const params = new URLSearchParams(searchParams)
            
            return(
                <Input 
                    placeholder="Buscar categoría..."
                    onChange={(e) => {
                      if (e.target.value) {
                        params.set('query', e.target.value);
                      } else {
                        params.delete('query');
                      }
                    }}
                    value={params.query}
                />
            )
        }
            
    
        // SearchCategoryPage
                     .
                     . 
                     .
        import InputSearch from "@/app/table/categorias/components/search-input";
    
        const SearchCategoryPage = async({searchParams}) => {
          const query = searchParams?.query || '';
          const categorias = await db.table_Categories.findMany({
            orderBy: {
              name: "asc",
            },
            // do your search query...
          })
          const subcategorias = await db.table_Subcategories.findMany({
            orderBy: {
              name: "asc",
            },
            // do your search query...
          })
        
          return(
            <div>
            <Navbar></Navbar>
              <Heading 
              title="Categorías" 
              description="Description"
              />
              <div className="pl-4 pr-4 w-[100%]">
              <InputSearch/>
              <Accordion type="single" collapsible className="w-full">
              {categorias
              .map((filteredCategoria) => (
                <AccordionItem key={filteredCategoria.id} value={filteredCategoria.name}>
                  <AccordionTrigger>{filteredCategoria.name}</AccordionTrigger>
                  <AccordionContent>
                    {subcategorias
                    .filter((subcategoria) => subcategoria.id_category === filteredCategoria.id)
                    .map((filteredSubcategoria) => (
                        <div>
                          <Button variant="link" key={filteredSubcategoria.id}>{filteredSubcategoria.name}</Button>  
                        </div>            
                        ))}
                  </AccordionContent>
                </AccordionItem>
              ))}
            </Accordion>
            </div>
            </div>
          )
        }
        
        export default SearchCategoryPage