Debounce user input for API calls
Use
debounce to delay function execution until user input pauses, reducing unnecessary API calls. The following example implements search within a product catalog (sample products are Laptop, Mouse, Keyboard, etc.).<Component
name="DebouncedSearch"
var.searchTerm=""
var.results="{[]}"
var.inProgress="{false}">
<VStack>
<TextBox
label="Search products:"
placeholder="Type to search..."
onDidChange="e => {
searchTerm = e;
inProgress = true;
// Only call API after 500ms of no typing
debounce(500, (term) => {
const response = Actions.callApi({
url: '/api/search',
method: 'POST',
body: { query: term }
});
results = response.status === 'ok' ? response.results : [];
inProgress = false;
}, e);
}"
/>
<Card when="{searchTerm.length > 0}">
<VStack>
<Text when="{inProgress}" variant="em">
Searching for: {searchTerm}
</Text>
<Fragment when="{!inProgress}">
<Fragment when="{results.length > 0}">
<H4>Found {pluralize(results.length, 'result', 'results')}</H4>
<List data="{results}">
{$item.name} ({$item.category}) - ${$item.price}
</List>
</Fragment>
<Text when="{results.length === 0}">
No results found
</Text>
</Fragment>
</VStack>
</Card>
</VStack>
</Component><App>
<DebouncedSearch />
</App>Search with debounced API calls
<Component
name="DebouncedSearch"
var.searchTerm=""
var.results="{[]}"
var.inProgress="{false}">
<VStack>
<TextBox
label="Search products:"
placeholder="Type to search..."
onDidChange="e => {
searchTerm = e;
inProgress = true;
// Only call API after 500ms of no typing
debounce(500, (term) => {
const response = Actions.callApi({
url: '/api/search',
method: 'POST',
body: { query: term }
});
results = response.status === 'ok' ? response.results : [];
inProgress = false;
}, e);
}"
/>
<Card when="{searchTerm.length > 0}">
<VStack>
<Text when="{inProgress}" variant="em">
Searching for: {searchTerm}
</Text>
<Fragment when="{!inProgress}">
<Fragment when="{results.length > 0}">
<H4>Found {pluralize(results.length, 'result', 'results')}</H4>
<List data="{results}">
{$item.name} ({$item.category}) - ${$item.price}
</List>
</Fragment>
<Text when="{results.length === 0}">
No results found
</Text>
</Fragment>
</VStack>
</Card>
</VStack>
</Component><App>
<DebouncedSearch />
</App>Key Points
Pass values as arguments: Always pass event values as additional arguments to
debounce rather than relying on closure capture:// ✅ Correct
onDidChange="e => debounce(500, (val) => handleSearch(val), e)"
// ❌ Wrong - 'e' may be undefined when executed
onDidChange="e => debounce(500, () => handleSearch(e))"
Use the same function reference: Each unique function source gets its own timer. Keep the function structure consistent:
// ✅ Correct - single timer
debounce(500, (val) => console.log('Value:', val), e)
// ❌ Wrong - creates different timers
if (condition) {
debounce(500, (val) => console.log('A:', val), e);
} else {
debounce(500, (val) => console.log('B:', val), e);
}
Common use cases:
- Search inputs — wait for typing to stop
- Form validation — delay until user pauses
- Auto-save — save changes after editing stops
- Filter controls — reduce computation frequency