- name
- svelte-patterns
- description
- Common Svelte patterns, best practices, and mistakes to avoid
# Svelte Patterns and Best Practices
This skill covers common component patterns, frequently made mistakes, best practices, and migration tips for Svelte development.
## Component Patterns
### Prop Patterns
```svelte
<!-- Basic props with defaults -->
<script>
let {
title = 'Default Title',
subtitle,
isActive = false,
items = []
} = $props();
</script>
<h1>{title}</h1>
{#if subtitle}
<h2>{subtitle}</h2>
{/if}
<!-- Rest props pattern -->
<script>
let { class: className, ...restProps } = $props();
</script>
<div class="container {className}" {...restProps}>
<slot />
</div>
```
### Event Handling Patterns
```svelte
<!-- Custom event callbacks via props -->
<script>
let {
onSubmit = () => {},
onChange = () => {},
onCancel
} = $props();
let value = $state('');
function handleSubmit(event) {
event.preventDefault();
onSubmit(value);
}
function handleChange(event) {
value = event.target.value;
onChange(value);
}
</script>
<form onsubmit={handleSubmit}>
<input type="text" value={value} oninput={handleChange} />
<button type="submit">Submit</button>
{#if onCancel}
<button type="button" onclick={onCancel}>Cancel</button>
{/if}
</form>
```
### Snippet Pattern (Render Props)
```svelte
<!-- Parent component -->
<script>
import DataList from './DataList.svelte';
let items = $state([
{ id: 1, name: 'Alice', role: 'Developer' },
{ id: 2, name: 'Bob', role: 'Designer' }
]);
</script>
<DataList {items}>
{#snippet itemRenderer(item)}
<div class="user-card">
<h3>{item.name}</h3>
<p>{item.role}</p>
</div>
{/snippet}
</DataList>
<!-- DataList.svelte -->
<script>
let { items, children } = $props();
</script>
<ul>
{#each items as item}
<li>
{@render children.itemRenderer(item)}
</li>
{/each}
</ul>
```
### Context API Pattern
```svelte
<!-- ThemeProvider.svelte -->
<script>
import { setContext } from 'svelte';
let { children } = $props();
let theme = $state('light');
setContext('theme', {
get current() { return theme; },
toggle: () => { theme = theme === 'light' ? 'dark' : 'light'; }
});
</script>
<div class="theme-{theme}">
{@render children()}
</div>
<!-- ThemeConsumer.svelte -->
<script>
import { getContext } from 'svelte';
const themeContext = getContext('theme');
</script>
<button onclick={themeContext.toggle}>
Current theme: {themeContext.current}
</button>
```
### Composition Pattern
```svelte
<!-- useCounter.svelte.js -->
export function useCounter(initialValue = 0) {
let count = $state(initialValue);
return {
get value() { return count; },
increment: () => count++,
decrement: () => count--,
reset: () => count = initialValue
};
}
<!-- Component.svelte -->
<script>
import { useCounter } from './useCounter.svelte.js';
const counter = useCounter(10);
</script>
<p>Count: {counter.value}</p>
<button onclick={counter.increment}>+</button>
<button onclick={counter.decrement}>-</button>
<button onclick={counter.reset}>Reset</button>
```
## Common Mistakes
### Mistake 1: Not Using Runes Properly
```svelte
<!-- WRONG: Mixing old and new syntax -->
<script>
let count = 0; // Not reactive in Svelte 5!
$: doubled = count * 2; // Old syntax
function increment() {
count += 1; // Won't trigger reactivity
}
</script>
<!-- CORRECT: Use runes -->
<script>
let count = $state(0);
let doubled = $derived(count * 2);
function increment() {
count += 1;
}
</script>
```
### Mistake 2: Mutating Props Directly
```svelte
<!-- WRONG: Mutating props -->
<script>
let { user } = $props();
function updateName(newName) {
user.name = newName; // Don't mutate props!
}
</script>
<!-- CORRECT: Use callbacks -->
<script>
let { user, onUpdateUser } = $props();
function updateName(newName) {
onUpdateUser({ ...user, name: newName });
}
</script>
```
### Mistake 3: Side Effects in Derived
```svelte
<!-- WRONG: Side effects in $derived -->
<script>
let count = $state(0);
let doubled = $derived(() => {
console.log('Calculating doubled'); // Side effect!
localStorage.setItem('count', count); // Side effect!
return count * 2;
});
</script>
<!-- CORRECT: Use $effect for side effects -->
<script>
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
console.log('Count changed to:', count);
localStorage.setItem('count', count.toString());
});
</script>
```
### Mistake 4: Missing Effect Cleanup
```svelte
<!-- WRONG: No cleanup -->
<script>
let isActive = $state(false);
$effect(() => {
if (isActive) {
const interval = setInterval(() => {
console.log('tick');
}, 1000);
// Missing cleanup! Memory leak!
}
});
</script>
<!-- CORRECT: Return cleanup function -->
<script>
let isActive = $state(false);
$effect(() => {
if (!isActive) return;
const interval = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(interval);
});
</script>
```
### Mistake 5: Incorrect Reactivity with Objects
```svelte
<!-- WRONG: Reassigning nested properties -->
<script>
let user = $state({ name: 'Alice', settings: { theme: 'light' } });
function toggleTheme() {
// This works but creates a new object unnecessarily
user = {
...user,
settings: { ...user.settings, theme: 'dark' }
};
}
</script>
<!-- CORRECT: Direct mutation with $state -->
<script>
let user = $state({ name: 'Alice', settings: { theme: 'light' } });
function toggleTheme() {
// $state provides deep reactivity
user.settings.theme = user.settings.theme === 'light' ? 'dark' : 'light';
}
</script>
```
### Mistake 6: Overusing Effects
```svelte
<!-- WRONG: Using effect for derived values -->
<script>
let count = $state(0);
let doubled = $state(0);
$effect(() => {
doubled = count * 2; // This should be $derived!
});
</script>
<!-- CORRECT: Use $derived -->
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
```
### Mistake 7: Not Handling Async Properly
```svelte
<!-- WRONG: Race conditions -->
<script>
let searchQuery = $state('');
let results = $state([]);
$effect(() => {
fetch(`/api/search?q=${searchQuery}`)
.then(res => res.json())
.then(data => {
results = data; // May set stale results!
});
});
</script>
<!-- CORRECT: Handle cancellation -->
<script>
let searchQuery = $state('');
let results = $state([]);
$effect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${searchQuery}`, { signal: controller.signal })
.then(res => res.json())
.then(data => results = data)
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
return () => controller.abort();
});
</script>
```
## Best Practices
### Practice 1: Component Organization
```svelte
<script>
// 1. Imports
import { getContext } from 'svelte';
import ChildComponent from './ChildComponent.svelte';
// 2. Props
let { title, items = [], onItemClick } = $props();
// 3. Context
const theme = getContext('theme');
// 4. State
let selectedIndex = $state(0);
let isExpanded = $state(false);
// 5. Derived values
let selectedItem = $derived(items[selectedIndex]);
let itemCount = $derived(items.length);
// 6. Effects
$effect(() => {
console.log('Selected item changed:', selectedItem);
});
// 7. Functions
function handleItemClick(index) {
selectedIndex = index;
onItemClick?.(items[index]);
}
function toggleExpanded() {
isExpanded = !isExpanded;
}
</script>
<!-- 8. Template -->
<div class="component">
<!-- content -->
</div>
<!-- 9. Styles -->
<style>
.component {
/* styles */
}
</style>
```
### Practice 2: Type Safety with TypeScript
```svelte
<script lang="ts">
interface User {
id: number;
name: string;
email: string;
}
let {
users,
onUserSelect
}: {
users: User[];
GitHubで見る