Svelte 5 State Management
SvelteBolt provides a powerful and modern approach to state management using TypeScript classes combined with Svelte's context API and Svelte 5's reactivity system.
Overview
The state management pattern in SvelteBolt leverages:
- TypeScript classes for organizing state logic
- Svelte's context API for dependency injection
- Svelte 5's
$state
runes for reactivity in.svelte.ts
files - Clean separation between different state concerns
This approach eliminates the complexity of traditional stores while providing powerful reactivity and type safety.
Basic App State
Setting Up App State
The main application state is defined in a TypeScript class:
import { getContext, setContext } from "svelte";
class AppState {
// Reactive state using Svelte 5's $state rune
user = $state(null);
theme = $state("light");
isLoading = $state(false);
constructor() {
// Initialize state or set up derived values
}
// Methods to update state
setUser(user) {
this.user = user;
}
toggleTheme() {
this.theme = this.theme === "light" ? "dark" : "light";
}
setLoading(loading) {
this.isLoading = loading;
}
}
export function setAppState() {
return setContext("appState", new AppState());
}
export function getAppState() {
return getContext<ReturnType<typeof setAppState>>("appState");
}
Using App State
In Parent Component (Setting Context)
In your root layout or parent component, initialize the state:
<script lang="ts">
import { setAppState } from "./app-state.svelte.ts";
// Initialize the app state context
setAppState();
</script>
<!-- Your app content -->
<slot />
In Child Components (Consuming State)
Any child component can access and use the state:
<script lang="ts">
import { getAppState } from "../app-state.svelte.ts";
const appState = getAppState();
function handleLogin() {
appState.setUser({ name: "John Doe", email: "[email protected]" });
}
function toggleTheme() {
appState.toggleTheme();
}
</script>
<!-- Reactive to state changes -->
<div class="theme-{appState.theme}">
{#if appState.user}
<p>Welcome, {appState.user.name}!</p>
{:else}
<button onclick="{handleLogin}">Login</button>
{/if}
<button onclick="{toggleTheme}">
Switch to {appState.theme === 'light' ? 'dark' : 'light'} mode
</button>
</div>
Key Benefits
1. Svelte 5 Reactivity in TypeScript
Using .svelte.ts
file extension enables Svelte 5's reactivity system in TypeScript files:
class AppState {
// These are reactive and will trigger UI updates
count = $state(0);
items = $state([]);
// Derived values are also reactive
get doubleCount() {
return this.count * 2;
}
increment() {
this.count++; // Automatically triggers reactivity
}
}
2. Type Safety
Full TypeScript support with proper typing:
interface User {
id: string;
name: string;
email: string;
}
class AppState {
user = $state<User | null>(null);
setUser(user: User) {
this.user = user;
}
}
3. No Store Complexity
Unlike traditional Svelte stores, this pattern:
- ✅ No need to import/export stores
- ✅ No subscription management
- ✅ No store boilerplate
- ✅ Natural OOP patterns
- ✅ Better IDE support
Creating Specific States
For different concerns, create separate state classes:
Editor State Example
// editor-state.svelte.ts
import { getContext, setContext } from "svelte";
class EditorState {
content = $state("");
cursor = $state({ line: 0, column: 0 });
isModified = $state(false);
language = $state("javascript");
constructor() {}
updateContent(newContent: string) {
this.content = newContent;
this.isModified = true;
}
setCursor(line: number, column: number) {
this.cursor = { line, column };
}
save() {
// Save logic here
this.isModified = false;
}
}
export function setEditorState() {
return setContext("editorState", new EditorState());
}
export function getEditorState() {
return getContext<ReturnType<typeof setEditorState>>("editorState");
}
Shopping Cart State Example
// cart-state.svelte.ts
import { getContext, setContext } from "svelte";
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
class CartState {
items = $state<CartItem[]>([]);
get total() {
return this.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
}
get itemCount() {
return this.items.reduce((sum, item) => sum + item.quantity, 0);
}
addItem(item: Omit<CartItem, "quantity">) {
const existing = this.items.find((i) => i.id === item.id);
if (existing) {
existing.quantity++;
} else {
this.items.push({ ...item, quantity: 1 });
}
}
removeItem(id: string) {
this.items = this.items.filter((item) => item.id !== id);
}
clear() {
this.items = [];
}
}
export function setCartState() {
return setContext("cartState", new CartState());
}
export function getCartState() {
return getContext<ReturnType<typeof setCartState>>("cartState");
}
Best Practices
1. File Naming Convention
- Use
.svelte.ts
extension for reactive TypeScript files - Name state files descriptively:
app-state.svelte.ts
,editor-state.svelte.ts
2. Context Keys
- Use descriptive context keys that match your state purpose
- Keep context scope as narrow as possible
3. State Organization
- Separate concerns into different state classes
- Keep related state and methods together in the same class
- Use getter methods for derived values
4. Initialization
- Initialize state in the appropriate parent component
- Ensure state is available before child components try to access it
Migration from Stores
If you're migrating from traditional Svelte stores:
// Old store approach
import { writable } from "svelte/store";
export const user = writable(null);
export const theme = writable("light");
// New class-based approach
class AppState {
user = $state(null);
theme = $state("light");
setUser(newUser) {
this.user = newUser;
}
toggleTheme() {
this.theme = this.theme === "light" ? "dark" : "light";
}
}
The class-based approach provides better organization, type safety, and eliminates the need for store subscriptions while maintaining full reactivity.