Skip to main content

Search Implementation

This guide covers implementing search functionality in your Twigz application.

Overview

Twigz provides comprehensive search capabilities for products, stores, and content discovery.

Basic Search Setup

// Basic product search
const searchProducts = async (query: string, filters?: SearchFilters) => {
  const results = await convex.query(api.shared.search.search, {
    query,
    type: "products",
    filters: {
      category: filters?.category,
      priceRange: filters?.priceRange,
      storeId: filters?.storeId
    },
    limit: 20
  });
  
  return results;
};
// Search stores
const searchStores = async (query: string, location?: Location) => {
  const results = await convex.query(api.shared.search.search, {
    query,
    type: "stores",
    filters: {
      location: location,
      category: "all"
    },
    limit: 10
  });
  
  return results;
};

Advanced Search Features

1. Quick Search (Autocomplete)

// Implement autocomplete
const [suggestions, setSuggestions] = useState([]);

const handleSearchInput = async (query: string) => {
  if (query.length < 2) {
    setSuggestions([]);
    return;
  }
  
  const results = await convex.query(api.shared.search.quickSearch, {
    query,
    limit: 5
  });
  
  setSuggestions(results);
};

2. Search History

// Save search history
const saveSearch = async (query: string) => {
  await convex.mutation(api.shared.search.saveSearchHistory, {
    query,
    userId: currentUser.id
  });
};

// Get search history
const getSearchHistory = async () => {
  const history = await convex.query(api.shared.search.getSearchHistory, {
    userId: currentUser.id,
    limit: 10
  });
  
  return history;
};
// Advanced search with multiple filters
const advancedSearch = async (searchParams: AdvancedSearchParams) => {
  const results = await convex.query(api.shared.search.advancedSearch, {
    query: searchParams.query,
    filters: {
      category: searchParams.category,
      priceRange: searchParams.priceRange,
      rating: searchParams.minRating,
      location: searchParams.location,
      storeId: searchParams.storeId
    },
    sortBy: searchParams.sortBy,
    sortOrder: searchParams.sortOrder,
    limit: searchParams.limit || 20
  });
  
  return results;
};

Search UI Components

1. Search Bar Component

const SearchBar = () => {
  const [query, setQuery] = useState("");
  const [suggestions, setSuggestions] = useState([]);
  const [showSuggestions, setShowSuggestions] = useState(false);

  const handleInputChange = async (value: string) => {
    setQuery(value);
    
    if (value.length >= 2) {
      const results = await convex.query(api.shared.search.quickSearch, {
        query: value,
        limit: 5
      });
      setSuggestions(results);
      setShowSuggestions(true);
    } else {
      setSuggestions([]);
      setShowSuggestions(false);
    }
  };

  return (
    <div className="search-container">
      <input
        type="text"
        value={query}
        onChange={(e) => handleInputChange(e.target.value)}
        placeholder="Search products, stores..."
        className="search-input"
      />
      
      {showSuggestions && suggestions.length > 0 && (
        <div className="suggestions-dropdown">
          {suggestions.map((suggestion, index) => (
            <div
              key={index}
              className="suggestion-item"
              onClick={() => {
                setQuery(suggestion.text);
                setShowSuggestions(false);
                performSearch(suggestion.text);
              }}
            >
              {suggestion.text}
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

2. Search Filters Component

const SearchFilters = ({ onFiltersChange }) => {
  const [filters, setFilters] = useState({
    category: "",
    priceRange: { min: 0, max: 1000 },
    rating: 0,
    sortBy: "relevance"
  });

  const handleFilterChange = (key, value) => {
    const newFilters = { ...filters, [key]: value };
    setFilters(newFilters);
    onFiltersChange(newFilters);
  };

  return (
    <div className="search-filters">
      <select
        value={filters.category}
        onChange={(e) => handleFilterChange("category", e.target.value)}
      >
        <option value="">All Categories</option>
        <option value="food">Food</option>
        <option value="electronics">Electronics</option>
        <option value="clothing">Clothing</option>
      </select>
      
      <input
        type="range"
        min="0"
        max="1000"
        value={filters.priceRange.max}
        onChange={(e) => handleFilterChange("priceRange", {
          ...filters.priceRange,
          max: parseInt(e.target.value)
        })}
      />
      
      <select
        value={filters.sortBy}
        onChange={(e) => handleFilterChange("sortBy", e.target.value)}
      >
        <option value="relevance">Relevance</option>
        <option value="price">Price</option>
        <option value="rating">Rating</option>
        <option value="newest">Newest</option>
      </select>
    </div>
  );
};

Search Results

1. Results Display

const SearchResults = ({ results, loading }) => {
  if (loading) {
    return <div className="loading">Searching...</div>;
  }

  if (!results || results.length === 0) {
    return <div className="no-results">No results found</div>;
  }

  return (
    <div className="search-results">
      {results.map((item) => (
        <SearchResultItem key={item.id} item={item} />
      ))}
    </div>
  );
};

2. Pagination

const SearchPagination = ({ currentPage, totalPages, onPageChange }) => {
  return (
    <div className="pagination">
      <button
        disabled={currentPage === 1}
        onClick={() => onPageChange(currentPage - 1)}
      >
        Previous
      </button>
      
      <span>Page {currentPage} of {totalPages}</span>
      
      <button
        disabled={currentPage === totalPages}
        onClick={() => onPageChange(currentPage + 1)}
      >
        Next
      </button>
    </div>
  );
};

Performance Optimization

import { useDebounce } from 'use-debounce';

const SearchComponent = () => {
  const [query, setQuery] = useState("");
  const [debouncedQuery] = useDebounce(query, 300);

  useEffect(() => {
    if (debouncedQuery) {
      performSearch(debouncedQuery);
    }
  }, [debouncedQuery]);
};

2. Caching Results

const useSearchCache = () => {
  const [cache, setCache] = useState(new Map());

  const getCachedResult = (query, filters) => {
    const key = `${query}-${JSON.stringify(filters)}`;
    return cache.get(key);
  };

  const setCachedResult = (query, filters, result) => {
    const key = `${query}-${JSON.stringify(filters)}`;
    setCache(prev => new Map(prev).set(key, result));
  };

  return { getCachedResult, setCachedResult };
};

Best Practices

  1. Debounce search input to avoid excessive API calls
  2. Cache search results for better performance
  3. Provide clear feedback during search operations
  4. Handle empty states gracefully
  5. Implement pagination for large result sets
  6. Use search history for better UX
  7. Test search relevance regularly
Search functionality is optimized for fast, relevant results with support for complex filtering and sorting options.