Skip to main content

Overview

useSearch is a hook that provides client-side search functionality with automatic filtering and debouncing. It works with arrays of primitives or objects, searching through serializable properties.

Import

import { useSearch } from "@zayne-labs/toolkit-react";

Signature

const useSearch = <TData>(
  initialData: TData[],
  delay?: number
) => {
  data: TData[];
  isLoading: boolean;
  query: string;
  setQuery: (query: string) => void;
}

Parameters

initialData
TData[]
required
The array of data to search through. Can be an array of primitives (strings, numbers, booleans) or objects.
delay
number
Debounce delay in milliseconds before filtering occurs. If not provided, filtering happens immediately.

Return Value

data
TData[]
The filtered data based on the current search query.
isLoading
boolean
Whether a search is currently in progress (useful when debouncing is enabled).
query
string
The current search query string.
setQuery
(query: string) => void
Function to update the search query.

Usage

import { useSearch } from "@zayne-labs/toolkit-react";

function FruitSearch() {
  const fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"];
  const { data, query, setQuery, isLoading } = useSearch(fruits, 300);

  return (
    <div>
      <input
        type="text"
        placeholder="Search fruits..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      
      {isLoading && <p>Searching...</p>}
      
      <ul>
        {data.map((fruit) => (
          <li key={fruit}>{fruit}</li>
        ))}
      </ul>
      
      {data.length === 0 && !isLoading && (
        <p>No results found for "{query}"</p>
      )}
    </div>
  );
}

Search Object Properties

import { useSearch } from "@zayne-labs/toolkit-react";

type User = {
  id: number;
  name: string;
  email: string;
  age: number;
};

function UserSearch() {
  const users: User[] = [
    { id: 1, name: "John Doe", email: "[email protected]", age: 30 },
    { id: 2, name: "Jane Smith", email: "[email protected]", age: 25 },
    { id: 3, name: "Bob Johnson", email: "[email protected]", age: 35 },
  ];

  const { data, query, setQuery } = useSearch(users, 200);

  return (
    <div>
      <input
        type="text"
        placeholder="Search by name, email, or age..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      
      <div>
        {data.map((user) => (
          <div key={user.id}>
            <h3>{user.name}</h3>
            <p>{user.email}</p>
            <p>Age: {user.age}</p>
          </div>
        ))}
      </div>
    </div>
  );
}
import { useSearch } from "@zayne-labs/toolkit-react";

type Product = {
  id: string;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
};

function ProductSearch({ products }: { products: Product[] }) {
  const { data, query, setQuery, isLoading } = useSearch(products, 300);

  return (
    <div>
      <div className="search-header">
        <input
          type="search"
          placeholder="Search products..."
          value={query}
          onChange={(e) => setQuery(e.target.value)}
        />
        <span>{data.length} results</span>
      </div>
      
      {isLoading ? (
        <p>Searching...</p>
      ) : (
        <div className="product-grid">
          {data.map((product) => (
            <div key={product.id} className="product-card">
              <h3>{product.name}</h3>
              <p className="price">${product.price}</p>
              <p className="category">{product.category}</p>
              {product.inStock ? (
                <span className="in-stock">In Stock</span>
              ) : (
                <span className="out-of-stock">Out of Stock</span>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Search with Clear Button

import { useSearch } from "@zayne-labs/toolkit-react";

function SearchWithClear() {
  const items = ["React", "Vue", "Angular", "Svelte", "Solid"];
  const { data, query, setQuery } = useSearch(items, 200);

  return (
    <div>
      <div className="search-input-wrapper">
        <input
          type="text"
          placeholder="Search frameworks..."
          value={query}
          onChange={(e) => setQuery(e.target.value)}
        />
        {query && (
          <button onClick={() => setQuery("")} aria-label="Clear search">

          </button>
        )}
      </div>
      
      <ul>
        {data.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}
import { useSearch } from "@zayne-labs/toolkit-react";

function NumberSearch() {
  const numbers = [10, 25, 30, 45, 50, 100, 250, 500];
  const { data, query, setQuery } = useSearch(numbers);

  return (
    <div>
      <input
        type="text"
        placeholder="Search numbers..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      
      <ul>
        {data.map((num, index) => (
          <li key={index}>{num}</li>
        ))}
      </ul>
    </div>
  );
}

Real-time Search Stats

import { useSearch } from "@zayne-labs/toolkit-react";

type Article = {
  id: number;
  title: string;
  author: string;
  published: boolean;
};

function ArticleSearch({ articles }: { articles: Article[] }) {
  const { data, query, setQuery, isLoading } = useSearch(articles, 300);

  return (
    <div>
      <input
        type="text"
        placeholder="Search articles..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      
      <div className="search-stats">
        <p>
          {isLoading ? (
            "Searching..."
          ) : (
            `Found ${data.length} of ${articles.length} articles`
          )}
        </p>
      </div>
      
      <div>
        {data.map((article) => (
          <div key={article.id}>
            <h3>{article.title}</h3>
            <p>By {article.author}</p>
            {article.published && <span>Published</span>}
          </div>
        ))}
      </div>
    </div>
  );
}

How It Works

Primitive Values

For strings, numbers, and booleans, the search performs a case-insensitive substring match:
const data = ["Apple", "Banana", "Cherry"];
// Query "app" matches "Apple"
// Query "3" in [1, 2, 30, 300] matches 30 and 300

Object Values

For objects, the search checks all serializable properties (strings, numbers, booleans) for matches:
const users = [
  { name: "John", age: 30, email: "[email protected]" },
];
// Query "john" matches (found in name and email)
// Query "30" matches (found in age)

Notes

  • Search is case-insensitive
  • Only serializable object properties (strings, numbers, booleans) are searched
  • The isLoading state is only set when debouncing is enabled (delay is provided)
  • Uses useDebouncedFn internally for debounced search
  • Updates only occur after mount to avoid unnecessary initial filtering
  • Built on top of isPlainObject from @zayne-labs/toolkit-type-helpers

Build docs developers (and LLMs) love