Reactivity & Stores
Svelte Reactivity & Stores Svelte is a radical new approach to building user interfaces. It compiles components into highly efficient imperative code that updat…
Svelte Reactivity & Stores
Svelte is a radical new approach to building user interfaces. It compiles components into highly efficient imperative code that updates the DOM surgically. Reactivity in Svelte is built into the language itself.
Reactive Declarations
Svelte automatically tracks dependencies and updates the DOM when state changes. Use the $ label for reactive declarations.
<script>
// Regular variable - reactive by assignment
let count = 0;
let name = 'Svelte';
// Reactive declaration - auto-updates when dependencies change
$: doubled = count * 2;
$: quadrupled = doubled * 2;
// Reactive statement - runs when dependencies change
$: {
console.log('Count changed to:', count);
console.log('Doubled:', doubled);
}
// Conditional reactive statement
$: if (count > 10) {
console.log('Count is greater than 10!');
}
function increment() {
count += 1; // Assignment triggers reactivity
}
function reset() {
count = 0;
}
</script>
<h1>{name}</h1>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<p>Quadrupled: {quadrupled}</p>
<button on:click={increment}>+1</button>
<button on:click={reset}>Reset</button>Arrays and Objects Reactivity
<script>
let todos = [
{ id: 1, text: 'Learn Svelte', done: false },
{ id: 2, text: 'Build app', done: false }
];
let user = {
name: 'John',
email: 'john@example.com',
age: 30
};
// ✅ Triggers reactivity - reassignment
function addTodo() {
todos = [...todos, { id: Date.now(), text: 'New todo', done: false }];
}
// ✅ Triggers reactivity - reassignment
function updateUser() {
user = { ...user, age: 31 };
}
// ❌ Won't trigger reactivity - mutation
function addTodoWrong() {
todos.push({ id: Date.now(), text: 'New', done: false });
// Need to reassign: todos = todos
}
// ✅ Fix: Reassign after mutation
function addTodoCorrect() {
todos.push({ id: Date.now(), text: 'New', done: false });
todos = todos; // Trigger reactivity
}
// ✅ Better: Use spread
function toggleTodo(id) {
todos = todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
);
}
// Array methods that need reassignment:
// push, pop, shift, unshift, splice, sort, reverse
</script>
<ul>
{#each todos as todo (todo.id)}
<li>
<input type="checkbox" checked={todo.done} on:change={() => toggleTodo(todo.id)} />
{todo.text}
</li>
{/each}
</ul>
<button on:click={addTodo}>Add Todo</button>Stores - Shared State
Svelte stores provide a way to share state across components without prop drilling.
Writable Stores
// stores.js
import { writable } from 'svelte/store';
// Create writable store with initial value
export const count = writable(0);
export const user = writable(null);
export const todos = writable([]);
// Store with custom logic
function createCounter() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const counter = createCounter();
// Component.svelte
<script>
import { count, user, counter } from './stores';
// Method 1: Subscribe manually
let countValue;
const unsubscribe = count.subscribe(value => {
countValue = value;
});
// Cleanup
import { onDestroy } from 'svelte';
onDestroy(unsubscribe);
// Method 2: Auto-subscribe with $
// $count is automatically reactive and auto-unsubscribes
function increment() {
count.update(n => n + 1);
// Or: count.set($count + 1);
}
function setUser() {
user.set({ name: 'John', email: 'john@example.com' });
}
</script>
<h1>Count: {$count}</h1>
<p>User: {$user?.name || 'None'}</p>
<button on:click={increment}>Increment</button>
<button on:click={() => counter.increment()}>Counter Increment</button>
<button on:click={setUser}>Set User</button>Readable Stores
import { readable } from 'svelte/store';
// Readable store - cannot be set from outside
export const time = readable(new Date(), set => {
const interval = setInterval(() => {
set(new Date());
}, 1000);
// Cleanup function (called when last subscriber unsubscribes)
return () => clearInterval(interval);
});
// Mouse position store
export const mouse = readable({ x: 0, y: 0 }, set => {
const handleMove = (event) => {
set({ x: event.clientX, y: event.clientY });
};
document.addEventListener('mousemove', handleMove);
return () => {
document.removeEventListener('mousemove', handleMove);
};
});
// Usage
<script>
import { time, mouse } from './stores';
</script>
<p>Time: {$time.toLocaleTimeString()}</p>
<p>Mouse: {$mouse.x}, {$mouse.y}</p>Derived Stores
import { derived, writable } from 'svelte/store';
export const todos = writable([
{ id: 1, text: 'Todo 1', done: false },
{ id: 2, text: 'Todo 2', done: true },
{ id: 3, text: 'Todo 3', done: false }
]);
// Derived from single store
export const completedTodos = derived(
todos,
$todos => $todos.filter(t => t.done)
);
export const activeTodos = derived(
todos,
$todos => $todos.filter(t => !t.done)
);
export const todoStats = derived(
todos,
$todos => ({
total: $todos.length,
completed: $todos.filter(t => t.done).length,
active: $todos.filter(t => !t.done).length
})
);
// Derived from multiple stores
const firstName = writable('John');
const lastName = writable('Doe');
export const fullName = derived(
[firstName, lastName],
([$firstName, $lastName]) => `${$firstName} ${$lastName}`
);
// Usage
<script>
import { completedTodos, activeTodos, todoStats } from './stores';
</script>
<p>Total: {$todoStats.total}</p>
<p>Completed: {$todoStats.completed}</p>
<p>Active: {$todoStats.active}</p>
<h3>Completed Todos:</h3>
{#each $completedTodos as todo}
<p>{todo.text}</p>
{/each}Custom Stores
// stores/auth.js
import { writable } from 'svelte/store';
function createAuthStore() {
const { subscribe, set, update } = writable({
user: null,
token: null,
isAuthenticated: false
});
return {
subscribe,
login: async (credentials) => {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
const data = await response.json();
set({
user: data.user,
token: data.token,
isAuthenticated: true
});
},
logout: () => {
set({
user: null,
token: null,
isAuthenticated: false
});
},
updateUser: (userData) => {
update(state => ({
...state,
user: { ...state.user, ...userData }
}));
}
};
}
export const auth = createAuthStore();
// Usage
<script>
import { auth } from './stores/auth';
async function handleLogin() {
await auth.login({ email, password });
}
</script>
{#if $auth.isAuthenticated}
<p>Welcome, {$auth.user.name}!</p>
<button on:click={() => auth.logout()}>Logout</button>
{:else}
<button on:click={handleLogin}>Login</button>
{/if}