Search
Example of how to implement debounced search with sorting.
First create state nodes and actions, then we create a scenario which executes a callback on every searchQuery state change. Inside of the callback we start a two promises race - if delay promise is resolved first, we are safe to perform a search request. Notice how we used searchQuery.events.changed
as a promise, in case it resolves before the delay, we just reject, which stops current callback execution; meanwhile new scenario callback with updated query is executed in parallel and starts the same flow.
type SearchResult = string;
type SortDirection = 'ascending' | 'descending';
const DEBOUNCE_TIMEOUT = 200;
const searchQuery = state('');
const setSearchQuery = action(searchQuery.set);
const resetSearch = action(() => setSearchQuery(''));
const searchResults = asyncState<SearchResult[]>([]);
const setSearchResults = action(searchResults.set);
const sortDirection = state<SortDirection>('ascending');
const setSortDirection = action(sortDirection.set);
const sortedSearchResults = selector(
[searchResults, sortDirection],
(results, direction) => {
return results.toSorted(
(a, b) => direction === 'ascending' ? a.localCompare(b) : b.localCompare(a)
);
}
);
scenario(
searchQuery.events.changed,
async (query) => {
await Promise.race([
delay(DEBOUNCE_TIMEOUT),
searchQuery.events.changed.then(() => Promise.reject()),
]);
if (query === '') {
setSearchResults([]);
return;
}
const encodedQuery = encodeURIComponent(query);
const resultsPromise = fetch(`/search?query={${encodedQuery}}`).then(response => response.json());
setSearchResults(resultsPromise);
}
);
scenario(sortedSearchResults.events.changed, (results) => {
console.log(`Sorted search results: ${results.join(', ')}`);
});