Tachyontachyon
Guides

Build a search box in React

A debounced, typo-tolerant search input backed by Tachyon, with no framework beyond React itself.

A minimal, debounced search-as-you-type component using tachyon-sdk directly from the browser. This assumes Tachyon is reachable from the client with a search-only API key — never expose the admin key to the browser. See Configuration → Authentication.

Install

npm install tachyon-sdk

The client

Create it once, outside your component, so it isn't recreated on every render:

// lib/tachyon.ts
import { Tachyon } from "tachyon-sdk";

export const tachyon = new Tachyon({
  url: import.meta.env.VITE_TACHYON_URL,
  apiKey: import.meta.env.VITE_TACHYON_SEARCH_KEY,
});

The component

import { useEffect, useRef, useState } from "react";
import { tachyon } from "./lib/tachyon";

interface Product {
  id: string;
  title: string;
  price: number;
}

export function SearchBox() {
  const [query, setQuery] = useState("");
  const [hits, setHits] = useState<{ document: Product; text_match: number }[]>([]);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    if (debounceRef.current) clearTimeout(debounceRef.current);

    if (!query) {
      setHits([]);
      return;
    }

    debounceRef.current = setTimeout(async () => {
      const results = await tachyon.collection<Product>("products").search({
        q: query,
        queryBy: ["title"],
        limit: 8,
      });
      setHits(results.hits);
    }, 150);

    return () => {
      if (debounceRef.current) clearTimeout(debounceRef.current);
    };
  }, [query]);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search…"
        aria-label="Search products"
      />
      <ul>
        {hits.map((hit) => (
          <li key={hit.document.id}>
            {hit.document.title} — ${(hit.document.price / 100).toFixed(2)}
          </li>
        ))}
      </ul>
    </div>
  );
}

Why 150ms

A short debounce keeps the search feeling instant without firing a request on every keystroke. Tachyon's own query latency at realistic corpus sizes is typically single-digit milliseconds (see the preliminary benchmark for measured numbers) — the debounce delay dominates perceived latency far more than the query itself does.

Handling typos without extra code

Nothing above opts into typo tolerance explicitly — it's on by default per collection, scaled to query length automatically. See Typo Tolerance if you need to tune or disable it.

Adding facets

Pass facet: ['brand'] to search() and render results.facets.brand as filter checkboxes — see Faceting for the exact response shape and how counts are computed.

On this page