# Build a search box in React
URL: /docs/guides/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](/docs/configuration#authentication).

## Install [#install]

```bash
npm install tachyon-sdk
```

## The client [#the-client]

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

```ts
// 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 [#the-component]

```tsx
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 [#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](/benchmarks) for measured numbers) — the debounce
delay dominates perceived latency far more than the query itself does.

## Handling typos without extra code [#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](/docs/typo-tolerance) if you need to tune or disable it.

## Adding facets [#adding-facets]

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