Documentation
Templates
Components
Data Display
Search with Filters

Search with Filters

A complete search and filter system with debouncing, URL synchronization, and server-side filtering.

Overview

This template provides:

  • Debounced search input
  • Multiple filter types (select, multi-select, date range)
  • URL state synchronization
  • Filter reset functionality
  • TanStack Query integration
  • Loading and empty states

Dependencies

npm install @tanstack/react-query nuqs
npx shadcn-ui@latest add input select button badge popover calendar
npm install date-fns  # for date formatting

Code

import { useState, useEffect } from 'react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Search, X } from 'lucide-react';
import { useDebounce } from '@/hooks/useDebounce';
 
interface SearchInputProps {
  value: string;
  onChange: (value: string) => void;
  placeholder?: string;
  debounceMs?: number;
}
 
export function SearchInput({
  value,
  onChange,
  placeholder = 'Search...',
  debounceMs = 300,
}: SearchInputProps) {
  const [localValue, setLocalValue] = useState(value);
  const debouncedValue = useDebounce(localValue, debounceMs);
 
  // Sync external value changes
  useEffect(() => {
    setLocalValue(value);
  }, [value]);
 
  // Emit debounced changes
  useEffect(() => {
    if (debouncedValue !== value) {
      onChange(debouncedValue);
    }
  }, [debouncedValue, onChange, value]);
 
  return (
    <div className="relative">
      <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
      <Input
        value={localValue}
        onChange={(e) => setLocalValue(e.target.value)}
        placeholder={placeholder}
        className="pl-9 pr-9"
      />
      {localValue && (
        <button
          onClick={() => {
            setLocalValue('');
            onChange('');
          }}
          className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
        >
          <X className="h-4 w-4" />
        </button>
      )}
    </div>
  );
}

useDebounce Hook

// hooks/useDebounce.ts
import { useState, useEffect } from 'react';
 
export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);
 
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
 
    return () => clearTimeout(timer);
  }, [value, delay]);
 
  return debouncedValue;
}

Features

URL State Synchronization

Filters are automatically synced to URL query parameters using nuqs:

  • Shareable URLs with active filters
  • Browser back/forward navigation works
  • Page refreshes preserve filters

Filter Types

TypeComponentUse Case
SearchDebounced inputText search
Single SelectSelect dropdownEnum values
Multi-SelectCheckbox popoverMultiple categories
Date RangeCalendar popoverDate filtering

Performance

  • Debounced search prevents excessive API calls
  • Query key includes all filters for proper caching
  • placeholderData keeps previous results during loading

Date Range Filter

Add date range filtering:

import { Calendar } from '@/components/ui/calendar';
import { format } from 'date-fns';
import { parseAsIsoDateTime } from 'nuqs';
 
// In filterParsers
const filterParsers = {
  // ... other parsers
  startDate: parseAsIsoDateTime,
  endDate: parseAsIsoDateTime,
};
 
// Date Range Picker Component
function DateRangePicker({ startDate, endDate, onChange }) {
  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button variant="outline">
          {startDate && endDate
            ? `${format(startDate, 'MMM d')} - ${format(endDate, 'MMM d')}`
            : 'Select dates'}
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-auto p-0" align="start">
        <Calendar
          mode="range"
          selected={{ from: startDate, to: endDate }}
          onSelect={(range) => onChange(range?.from, range?.to)}
        />
      </PopoverContent>
    </Popover>
  );
}

Related