useFuzzySearch

Fuzzy search for flat lists powered by Fuse.js.

useFuzzySearch wraps Fuse.js to provide fast, client-side fuzzy matching for simple lists. It memoizes the index for performance and returns filtered results with a minimal API.

Code

import { Dispatch, SetStateAction, useMemo, useState } from 'react';

import Fuse, { FuseOptionKey } from 'fuse.js';

type UseFuzzySearchOptions<T> = {
  searchKeys: FuseOptionKey<T>[];
  threshold?: number;
  ignoreLocation?: boolean;
};

/**
 * A hook that provides fuzzy search functionality for an array of items.
 *
 * @param items - Array of items to search
 * @param options - Search configuration options
 * @returns Array containing query state, setter, and filtered items
 */
export function useFuzzySearch<T>(
  items: T[],
  options: UseFuzzySearchOptions<T> = { searchKeys: [] },
): [string, Dispatch<SetStateAction<string>>, T[], boolean] {
  const [query, setQuery] = useState<string>('');
  const { searchKeys, threshold = 0.2, ignoreLocation = true } = options;

  // Create a memoized Fuse instance
  const fuse = useMemo(() => {
    return new Fuse(items, {
      keys: searchKeys as FuseOptionKey<T>[],
      threshold,
      ignoreLocation,
    });
  }, [items, searchKeys, threshold, ignoreLocation]);

  // Filter items based on the search query
  const filteredItems = useMemo(() => {
    if (!query) return items;
    return fuse.search(query).map((result) => result.item);
  }, [fuse, items, query]);

  const isEmpty = useMemo(() => {
    return filteredItems.length === 0;
  }, [filteredItems]);

  return [query, setQuery, filteredItems, isEmpty];
}

Example

  • Next.js
  • React
  • TypeScript
  • Tailwind CSS
  • Radix UI
  • Shadcn UI

Usage

'use client';

import React from 'react';

import { Search } from 'lucide-react';

import { Input } from '@/components/intuitive-ui/(native)/input';

import { useAutoFocusedInput } from '@/hooks/use-auto-focused-input';
import { useFuzzySearch } from '@/hooks/use-fuzzy-search';

import Container from '@/app/repository/[topic]/[slug]/_components/container';

const data = [
  'Next.js',
  'React',
  'TypeScript',
  'Tailwind CSS',
  'Radix UI',
  'Shadcn UI',
];

const BasicExample: React.FC = () => {
  const [query, setQuery, results] = useFuzzySearch(data, {
    searchKeys: [],
  });
  const inputRef = useAutoFocusedInput();

  return (
    <div className="flex flex-col gap-4">
      <Container>
        <div className="flex flex-col gap-2">
          <Input
            LeadingIcon={Search}
            ref={inputRef}
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="Search technologies"
            className="max-w-sm min-w-sm"
          />
          <ul className="min-h-32 list-disc pl-6 text-sm">
            {results.map((item) => (
              <li key={item}>{item}</li>
            ))}
          </ul>
        </div>
      </Container>
    </div>
  );
};

export default BasicExample;