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.).
<App
var.searchTerm=""
var.results="{[]}"
var.invoked="{0}"
var.changed="{0}"
var.inProgress="{false}">
<!--
products: [
{ id: 1, name: 'Laptop', price: 999, category: 'Electronics' },
{ id: 2, name: 'Mouse', price: 29, category: 'Electronics' },
{ id: 3, name: 'Keyboard', price: 79, category: 'Electronics' },
{ id: 4, name: 'Monitor', price: 299, category: 'Electronics' },
{ id: 5, name: 'Desk Chair', price: 199, category: 'Furniture' },
{ id: 6, name: 'Desk Lamp', price: 49, category: 'Furniture' }
]
-->
<VStack>
<TextBox
label="Search products:"
placeholder="Type to search..."
onDidChange="e => {
searchTerm = e;
changed++;
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;
invoked++;
}, e);
}"
/>
<Text>Changed/Invoked: {changed} / {invoked}</Text>
<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>
</App>Search with debounced API calls
<App
var.searchTerm=""
var.results="{[]}"
var.invoked="{0}"
var.changed="{0}"
var.inProgress="{false}">
<!--
products: [
{ id: 1, name: 'Laptop', price: 999, category: 'Electronics' },
{ id: 2, name: 'Mouse', price: 29, category: 'Electronics' },
{ id: 3, name: 'Keyboard', price: 79, category: 'Electronics' },
{ id: 4, name: 'Monitor', price: 299, category: 'Electronics' },
{ id: 5, name: 'Desk Chair', price: 199, category: 'Furniture' },
{ id: 6, name: 'Desk Lamp', price: 49, category: 'Furniture' }
]
-->
<VStack>
<TextBox
label="Search products:"
placeholder="Type to search..."
onDidChange="e => {
searchTerm = e;
changed++;
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;
invoked++;
}, e);
}"
/>
<Text>Changed/Invoked: {changed} / {invoked}</Text>
<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>
</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