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-sdkThe 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.