Skip to content
LuoForge/Tungsten
← All recipes

Async option loading

A Combobox driven by useAsyncOptions: debounced server-side search with loading / empty states. shouldFilter is off so the server owns filtering.

Preview

Source

apps/docs/app/recipes/combobox-async/recipe.tsx
'use client';

import { Combobox, useAsyncOptions, type ComboboxOption } from '@hey-mike/tungsten';

const PEOPLE = ['Ada Lovelace', 'Alan Turing', 'Grace Hopper', 'Katherine Johnson', 'Edsger Dijkstra'];

// Fake server: filter + latency.
function searchPeople(query: string): Promise<ComboboxOption[]> {
  return new Promise((resolve) => {
    setTimeout(() => {
      const q = query.toLowerCase();
      resolve(
        PEOPLE.filter((p) => p.toLowerCase().includes(q)).map((p) => ({ id: p, label: p })),
      );
    }, 400);
  });
}

export default function ComboboxAsyncRecipe() {
  const { options, isLoading, error, onInputChange } = useAsyncOptions(searchPeople);
  return (
    <div className="w-72">
      <Combobox
        label="Search people"
        options={options}
        isLoading={isLoading}
        error={error}
        onInputChange={onInputChange}
        shouldFilter={false}
        placeholder="Type a name…"
        emptyMessage="No people found"
      />
    </div>
  );
}
'use client';

import { Combobox, useAsyncOptions, type ComboboxOption } from '@hey-mike/tungsten';

const PEOPLE = ['Ada Lovelace', 'Alan Turing', 'Grace Hopper', 'Katherine Johnson', 'Edsger Dijkstra'];

// Fake server: filter + latency.
function searchPeople(query: string): Promise<ComboboxOption[]> {
  return new Promise((resolve) => {
    setTimeout(() => {
      const q = query.toLowerCase();
      resolve(
        PEOPLE.filter((p) => p.toLowerCase().includes(q)).map((p) => ({ id: p, label: p })),
      );
    }, 400);
  });
}

export default function ComboboxAsyncRecipe() {
  const { options, isLoading, error, onInputChange } = useAsyncOptions(searchPeople);
  return (
    <div className="w-72">
      <Combobox
        label="Search people"
        options={options}
        isLoading={isLoading}
        error={error}
        onInputChange={onInputChange}
        shouldFilter={false}
        placeholder="Type a name…"
        emptyMessage="No people found"
      />
    </div>
  );
}