Async Loading
Load options from an API with built-in debounce, caching, and loading states. No external state management needed.
Async Search
Async loading with 300ms debounce · Cached for 30s
Pass an async config object instead of static options:
import { useVirtualSelect } from 'virtualized-ui';
import type { AsyncConfig } from 'virtualized-ui';
const asyncConfig: AsyncConfig<User> = {
loadOptions: async (inputValue) => {
const res = await fetch(
`/api/users?q=${inputValue}`
);
return res.json();
},
debounceMs: 300,
loadOnOpen: true,
cacheTtlMs: 30000,
};
const select = useVirtualSelect({
getOptionValue: (u) => u.id,
getOptionLabel: (u) => u.name,
async: asyncConfig,
searchable: true,
});
// Loading state
select.isLoading; // true while fetchingThe hook handles debouncing, caching, and cleanup automatically. When async is provided, static options are ignored.
AsyncConfig
| Option | Type | Default | Description |
|---|---|---|---|
loadOptions | (input: string) => Promise<T[] | OptionGroup<T>[]> | required | Fetch function |
debounceMs | number | 300 | Debounce delay in ms |
loadOnOpen | boolean | true | Call loadOptions when menu opens |
cacheTtlMs | number | 0 | Cache TTL in ms (0 = no cache) |
Loading & Empty States
The isLoading return value lets you render custom loading UI:
const { isLoading, flattenedItems } = useVirtualSelect({ async: asyncConfig });
if (isLoading) return <Spinner />;
if (flattenedItems.length === 0) return <p>No results</p>;
Or customize via component slots:
<VirtualSelect
async={asyncConfig}
components={{
Loading: () => <div>Searching...</div>,
NoOptions: ({ searchValue }) => (
<div>No results for "{searchValue}"</div>
),
}}
/>