Svelte stores provide a reactive state management solution that works seamlessly within and outside components.
What Are Stores?
Stores are objects that hold reactive values. When the value changes, all components that subscribe to the store automatically update.
Writable Stores
Creating a Writable Store
javascript
import { writable } from 'svelte/store';
const count = writable(0);
// Subscribe to changes
const unsubscribe = count.subscribe(value => {
console.log(value);
});
// Update value
count.set(1);
count.update(n => n + 1);
// Unsubscribe
unsubscribe();Using Writable Stores in Components
svelte
<script>
import { count } from './stores';
function increment() {
count.update(n => n + 1);
}
function decrement() {
count.update(n => n - 1);
}
function reset() {
count.set(0);
}
</script>
<h1>{$count}</h1>
<button on:click={increment}>+</button>
<button on:click={decrement}>-</button>
<button on:click={reset}>Reset</button>Writable Store with Methods
javascript
// stores/user.js
import { writable } from 'svelte/store';
function createUserStore() {
const { subscribe, set, update } = writable(null);
return {
subscribe,
login: (userData) => set(userData),
logout: () => set(null),
updateProfile: (updates) => {
update(user => ({ ...user, ...updates }));
}
};
}
export const user = createUserStore();Readable Stores
Readable stores cannot be updated from the outside:
javascript
import { readable, derived } from 'svelte/store';
import { browser } from '$app/environment';
// Time store
export const time = readable(new Date(), (set) => {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return () => clearInterval(interval);
});
// Location store
export const location = readable(null, (set) => {
if (browser && navigator.geolocation) {
const watcher = navigator.geolocation.watchPosition(
position => {
set({
lat: position.coords.latitude,
lng: position.coords.longitude
});
},
error => console.error(error)
);
return () => navigator.geolocation.clearWatch(watcher);
}
});Derived Stores
Derive values from other stores:
javascript
import { derived } from 'svelte/store';
const firstName = writable('John');
const lastName = writable('Doe');
// Simple derived store
export const fullName = derived(
[firstName, lastName],
([$first, $last]) => `${$first} ${$last}`
);
// Derived with custom logic
export const greeting = derived(
fullName,
($fullName, set) => {
set(`Hello, ${$fullName}!`);
// Cleanup
return () => console.log('Greeting updated');
},
'Hello!' // Initial value
);
// Derived from single store
export const doubled = derived(
count,
$count => $count * 2
);Custom Stores
Creating a Reusable Store Pattern
javascript
// stores/createStore.js
import { writable, derived } from 'svelte/store';
export function createStore(initial) {
const store = writable(initial);
const { subscribe, set, update } = store;
return {
subscribe,
update,
set,
reset: () => set(initial),
patch: (updates) => {
update(current => ({ ...current, ...updates }));
}
};
}
// Usage
export const user = createStore({ name: '', email: '' });Todo Store Example
javascript
// stores/todos.js
import { writable, derived } from 'svelte/store';
function createTodos() {
const { subscribe, update } = writable([]);
return {
subscribe,
add: (text) => {
update(todos => [
...todos,
{ id: Date.now(), text, completed: false }
]);
},
toggle: (id) => {
update(todos =>
todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
},
remove: (id) => {
update(todos => todos.filter(todo => todo.id !== id));
},
clear: () => {
update(() => []);
}
};
}
export const todos = createTodos();
// Derived stores
export const completedCount = derived(
todos,
$todos => $todos.filter(t => t.completed).length
);
export const activeCount = derived(
todos,
$todos => $todos.filter(t => !t.completed).length
);Store Persistence
LocalStorage Store
javascript
// stores/persisted.js
import { writable } from 'svelte/store';
function persisted(key, initial) {
const stored = localStorage.getItem(key);
const store = writable(stored ? JSON.parse(stored) : initial);
store.subscribe($value => {
localStorage.setItem(key, JSON.stringify($value));
});
return store;
}
export const user = persisted('user', null);
export const theme = persisted('theme', 'light');Store Composition
Combining Multiple Stores
javascript
// stores/cart.js
import { derived } from 'svelte/store';
import { products } from './products';
import { cartItems } from './cartItems';
export const cartTotal = derived(
[products, cartItems],
([$products, $cartItems]) => {
return $cartItems.reduce((total, item) => {
const product = $products.find(p => p.id === item.productId);
return total + (product?.price || 0) * item.quantity;
}, 0);
}
);
export const cartItemCount = derived(
cartItems,
$cartItems => $cartItems.reduce((sum, item) => sum + item.quantity, 0)
);Async Stores
Fetching Data
javascript
// stores/posts.js
import { writable } from 'svelte/store';
function createAsyncStore(fetcher) {
const store = writable({
data: null,
loading: true,
error: null
});
async function load() {
store.update(s => ({ ...s, loading: true, error: null }));
try {
const data = await fetcher();
store.set({ data, loading: false, error: null });
} catch (error) {
store.update(s => ({
...s,
loading: false,
error: error.message
}));
}
}
return {
subscribe: store.subscribe,
load
};
}
export const posts = createAsyncStore(async () => {
const res = await fetch('/api/posts');
return res.json();
});Testing Stores
javascript
// tests/stores/counter.spec.js
import { tick } from 'svelte';
import { get } from 'svelte/store';
import { count } from '../stores';
describe('Counter Store', () => {
it('should increment', () => {
count.set(0);
count.update(n => n + 1);
expect(get(count)).toBe(1);
});
it('should decrement', () => {
count.set(5);
count.update(n => n - 1);
expect(get(count)).toBe(4);
});
});Best Practices
- 1Keep stores focused - One store per domain
- 2Use derived stores for computed values
- 3Persist critical state - LocalStorage for important data
- 4Avoid direct mutations - Use update methods
- 5Clean up subscriptions - Always unsubscribe
Svelte stores provide a simple yet powerful way to manage state in your applications.