Event Handling Standards
Overview
This document defines the standard approach for handling user interactions and events in this Svelte application.
Core Principle: Event-Driven Architecture
Standard: Use explicit event handlers over reactive effects ($effect) for user interactions.
Why:
- ✅ Clear, predictable flow
- ✅ Easier to debug
- ✅ Better control over when logic runs
- ✅ Avoids reactive loops and race conditions
Event Handler Naming Convention
Standard: Use the handleX naming pattern for all event handlers.
Common Patterns
| Pattern | Use Case | Example |
|---|---|---|
handleXClick | Click events | handleSearchClick() |
handleXSubmit | Form submissions | handleModalSubmit() |
handleXCancel | Cancel/close actions | handleModalCancel() |
handleXKeydown | Keyboard events | handleSearchKeydown() |
Example:
function handleSearchClick() {
if (!searchInput.trim()) return;
searchQuery = searchInput;
performSearch();
}
function handleSearchKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
handleSearchClick();
}
}
Event-Driven vs Reactive Patterns
Use Event Handlers For:
✅ User interactions - Buttons, forms, keyboard input
function handleSearchClick() {
searchQuery = searchInput;
performSearch();
}
Use $effect Only For:
✅ Side effects - Browser APIs, event listeners, cleanup
$effect(() => {
const handler = () => {
currentPath = window.location.hash || '#/';
};
window.addEventListener('hashchange', handler);
return () => window.removeEventListener('hashchange', handler);
});
Never Use $effect For:
User interaction logic - Use handlers instead
// Bad - Creates reactive loops
$effect(() => {
if (searchInput) {
performSearch();
}
});
// ✅ Good - Explicit control
function handleSearchClick() {
performSearch();
}
Central Logic Functions
Standard: Extract complex logic into functions called by event handlers.
// Event Handlers - Respond to user actions
function handleSearchClick() {
searchQuery = searchInput;
currentPage = 1;
performSearch(); // Call central logic
}
function handlePaginationClick(newPage: number) {
currentPage = newPage;
performSearch(); // Reuse central logic
}
// Core Logic - Called from multiple handlers
async function performSearch() {
// Update URL
const url = new URL(window.location.href);
url.searchParams.set('q', searchQuery);
window.history.pushState({}, '', url);
// Fetch results
isLoading = true;
try {
searchResults = await fetchResults(searchQuery, currentPage);
} finally {
isLoading = false;
}
}
Benefits:
- Handlers stay simple
- Logic is reusable
- Easy to test
Component Event Flow
Standard Flow:
User Action → Event Handler → Update State → Central Logic → UI Updates
Example:
// State
let isModalOpen = $state(false);
let inputValue = $state('');
let isSubmitting = $state(false);
// Handlers
function handleAddClick() {
inputValue = '';
isModalOpen = true;
}
function handleModalCancel() {
inputValue = '';
isModalOpen = false;
}
async function handleModalSubmit() {
if (!inputValue.trim()) return;
isSubmitting = true;
try {
await saveItem(inputValue);
handleModalCancel(); // Close on success
} catch (error) {
console.error('Failed to save:', error);
} finally {
isSubmitting = false;
}
}
Inline vs Named Handlers
Use Named Handlers For:
- Complex logic (>2 lines)
- Reused logic
- Logic that needs testing
Use Inline Handlers For:
- Simple state toggles:
onclick={() => (isOpen = !isOpen)} - Simple assignments:
onclick={() => (count = 0)}
Form Handling
Standard pattern:
// State
let formData = $state({ name: '', email: '' });
let errors = $state<Record<string, string>>({});
let isSubmitting = $state(false);
// Validation
function validateForm(): boolean {
errors = {};
if (!formData.name.trim()) {
errors.name = 'Name is required';
}
return Object.keys(errors).length === 0;
}
// Handler
async function handleSubmit(event: Event) {
event.preventDefault();
if (!validateForm()) return;
isSubmitting = true;
try {
await saveFormData(formData);
} finally {
isSubmitting = false;
}
}
Lifecycle vs Events
Use onMount For:
- Initial data loading from URL
- Component initialization
onMount(() => {
const query = new URLSearchParams(window.location.search).get('q');
if (query) {
searchInput = query;
performSearch();
}
});
Use Event Handlers For:
- All user interactions after mount
Anti-Patterns to Avoid
Don't use $effect for user interactions
// Bad
$effect(() => {
if (inputValue) doSomething();
});
// ✅ Good
function handleInputChange() {
if (inputValue) doSomething();
}
Don't put complex logic inline
<!-- Bad -->
<Button onclick={() => {
if (!validate()) return;
isSubmitting = true;
fetch('/api/save', { ... })
}}>Save</Button>
<!-- ✅ Good -->
<Button onclick={handleSave}>Save</Button>
Don't forget preventDefault on forms
// Bad
function handleSubmit() {
saveData();
}
// ✅ Good
function handleSubmit(event: Event) {
event.preventDefault();
saveData();
}
Quick Reference
When to Use What
| Scenario | Solution |
|---|---|
| Button click | handleXClick() |
| Form submit | handleXSubmit(event) with event.preventDefault() |
| Keyboard shortcut | handleXKeydown(event) |
| Complex logic reused by handlers | Central function (no handle prefix) |
| Initial load | onMount() |
| Browser API side effects | $effect() with cleanup |
| User interaction logic | Never $effect() |
Examples from Codebase
- search.svelte -
handleSearchClick(),handlePaginationClick(),performSearch() - integration.svelte -
handleAddClick(),handleModalCancel(),handleModalSubmit()
Last Updated: 2025-12-30