Svelte provides powerful built-in state management capabilities. This guide explores advanced patterns and techniques for managing complex application state effectively.
Understanding Svelte Stores
Svelte stores provide reactive state management out of the box. Let's explore advanced usage patterns.
Custom Store Creation
Create specialized stores with derived behavior:
import { writable, derived } from 'svelte/store'
function createTodoStore() {
const { subscribe, set, update } = writable([])
return {
subscribe,
add: (todo) => update(todos => [...todos, todo]),
remove: (id) => update(todos =>
todos.filter(t => t.id !== id)
),
toggle: (id) => update(todos =>
todos.map(t => t.id === id
? { ...t, completed: !t.completed }
: t
)
),
clear: () => set([])
}
}
export const todos = createTodoStore()
// Derived stores for filtering
export const completedTodos = derived(
todos,
$todos => $todos.filter(t => t.completed)
)
export const activeTodos = derived(
todos,
$todos => $todos.filter(t => !t.completed)
)
Persistent Store
Implement localStorage persistence:
function createPersistentStore(key, initialValue) {
const storedValue = typeof window !== 'undefined'
? JSON.parse(localStorage.getItem(key)) ?? initialValue
: initialValue
const store = writable(storedValue)
const { subscribe, set, update } = store
return {
subscribe,
set: (value) => {
localStorage.setItem(key, JSON.stringify(value))
set(value)
},
update: (fn) => {
update(value => {
const updated = fn(value)
localStorage.setItem(key, JSON.stringify(updated))
return updated
})
}
}
}
export const settings = createPersistentStore('app-settings', {
theme: 'light',
language: 'en'
})
Context API Integration
Use Svelte's context API for component tree state:
// stores/theme-context.js
import { setContext, getContext } from 'svelte'
import { writable } from 'svelte/store'
const THEME_KEY = Symbol()
export function createThemeContext() {
const theme = writable('light')
setContext(THEME_KEY, {
theme,
toggleTheme: () => theme.update(t =>
t === 'light' ? 'dark' : 'light'
)
})
}
export function getThemeContext() {
return getContext(THEME_KEY)
}
// App.svelte
<script>
import { createThemeContext } from './stores/theme-context'
createThemeContext()
</script>
// ThemeToggle.svelte
<script>
import { getThemeContext } from './stores/theme-context'
const { theme, toggleTheme } = getThemeContext()
</script>
<button on:click={toggleTheme}>
Current theme: {$theme}
</button>
Advanced Store Patterns
Async Store
Handle asynchronous state updates:
function createAsyncStore(asyncFn) {
const { subscribe, set, update } = writable({
loading: false,
error: null,
data: null
})
async function load(...args) {
update(s => ({ ...s, loading: true, error: null }))
try {
const data = await asyncFn(...args)
set({ loading: false, error: null, data })
return data
} catch (error) {
set({ loading: false, error, data: null })
throw error
}
}
return {
subscribe,
load
}
}
// Usage
const userStore = createAsyncStore(async (id) => {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) throw new Error('Failed to fetch user')
return response.json()
})
Form State Management
Create a form state manager:
function createFormStore(initialValues, validate) {
const values = writable(initialValues)
const errors = writable({})
const touched = writable({})
const isSubmitting = writable(false)
async function handleSubmit(onSubmit) {
const currentValues = get(values)
const validationErrors = validate(currentValues)
if (Object.keys(validationErrors).length > 0) {
errors.set(validationErrors)
return
}
isSubmitting.set(true)
try {
await onSubmit(currentValues)
} finally {
isSubmitting.set(false)
}
}
function setFieldValue(field, value) {
values.update(v => ({ ...v, [field]: value }))
touched.update(t => ({ ...t, [field]: true }))
}
return {
values: { subscribe: values.subscribe },
errors: { subscribe: errors.subscribe },
touched: { subscribe: touched.subscribe },
isSubmitting: { subscribe: isSubmitting.subscribe },
setFieldValue,
handleSubmit
}
}
// Usage
const loginForm = createFormStore(
{ email: '', password: '' },
values => {
const errors = {}
if (!values.email) errors.email = 'Required'
if (!values.password) errors.password = 'Required'
return errors
}
)
State Synchronization
Implement cross-tab state synchronization:
function createSyncedStore(key, initialValue) {
const store = writable(initialValue)
if (typeof window !== 'undefined') {
// Load initial value from storage
const stored = localStorage.getItem(key)
if (stored) store.set(JSON.parse(stored))
// Subscribe to store changes
store.subscribe(value => {
localStorage.setItem(key, JSON.stringify(value))
// Broadcast change to other tabs
window.dispatchEvent(new CustomEvent(key, {
detail: value
}))
})
// Listen for changes from other tabs
window.addEventListener(key, (event) => {
store.set(event.detail)
})
}
return store
}
Performance Optimization
Memoized Derived Stores
Implement efficient computed values:
function memoizedDerived(stores, fn, initialValue = undefined) {
let previousInputs = undefined
let previousOutput = initialValue
return derived(stores, ($stores, set) => {
const currentInputs = Array.isArray($stores)
? $stores
: [$stores]
if (!previousInputs ||
!currentInputs.every((v, i) => v === previousInputs[i])) {
previousInputs = currentInputs
previousOutput = fn(currentInputs)
set(previousOutput)
}
}, initialValue)
}
Batch Updates
Implement batch updates for better performance:
function createBatchStore(initialState) {
const { subscribe, update } = writable(initialState)
let batchTimeout
function batchUpdate(updates) {
clearTimeout(batchTimeout)
batchTimeout = setTimeout(() => {
update(state => {
let newState = { ...state }
updates.forEach(([key, value]) => {
newState[key] = value
})
return newState
})
}, 0)
}
return {
subscribe,
batchUpdate
}
}
Testing Store Logic
Implement testable store behavior:
// store.test.js
import { get } from 'svelte/store'
import { todos } from './todo-store'
describe('Todo Store', () => {
beforeEach(() => {
todos.clear()
})
test('adds todo', () => {
todos.add({ id: 1, text: 'Test', completed: false })
expect(get(todos)).toHaveLength(1)
})
test('toggles todo', () => {
todos.add({ id: 1, text: 'Test', completed: false })
todos.toggle(1)
expect(get(todos)[0].completed).toBe(true)
})
})
Best Practices
- Store Organization
- Group related stores
- Use descriptive names
- Document store interfaces
- Performance
- Use derived stores for computed values
- Implement memoization where needed
- Batch updates when possible
- Testing
- Test store logic in isolation
- Mock external dependencies
- Verify state transitions
Conclusion
Svelte's state management capabilities offer powerful solutions for complex applications:
- Built-in reactive stores
- Context API for component trees
- Custom store patterns
- Performance optimization techniques
Key takeaways:
- Use appropriate store types for different needs
- Implement proper synchronization
- Consider performance implications
- Test store logic thoroughly
- Follow best practices for organization
By mastering these advanced state management techniques, you can build more maintainable and performant Svelte applications.