React Context in Svelte? Let's Hack State Management Like a Pro!
If you’re coming from React and love Context API + useReducer, Svelte 5’s new Runes make similar patterns possible - but simpler. Let’s explore both basic and advanced implementations.
Here’s how common React patterns translate to Svelte 5’s new reactivity system using runes:
1. Basic Context Implementation
React Approach
// ThemeContext.jsx
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme(prev =>
prev === 'light' ? 'dark' : 'light'
);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
return useContext(ThemeContext);
}
Svelte 5 Approach
<!-- ThemeContext.svelte -->
<script>
import { setContext } from 'svelte';
const ThemeSymbol = Symbol();
let theme = $state('light');
setContext(ThemeSymbol, {
theme,
toggleTheme: () => theme = theme === 'light' ? 'dark' : 'light'
});
</script>
<slot />
2. Reducer Pattern Implementation
React (useReducer)
// TodoReducer.jsx
import { useReducer, createContext } from 'react';
const TodoContext = createContext();
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, action.payload]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? {...todo, completed: !todo.completed}
: todo
)
};
default:
return state;
}
};
export function TodoProvider({ children }) {
const [state, dispatch] = useReducer(todoReducer, {
todos: [],
filter: 'all'
});
return (
<TodoContext.Provider value={{ ...state, dispatch }}>
{children}
</TodoContext.Provider>
);
}
Svelte 5 (Runes Pattern)
<!-- TodoStore.svelte -->
<script>
import { setContext } from 'svelte';
// Reactive state
const todos = $state([]);
const filter = $state('all');
// Reducer-like actions
const dispatch = (action) => {
switch (action.type) {
case 'ADD_TODO':
todos = [...todos, {
id: crypto.randomUUID(),
text: action.payload,
completed: false
}];
break;
case 'TOGGLE_TODO':
todos = todos.map(todo =>
todo.id === action.payload
? {...todo, completed: !todo.completed}
: todo
);
break;
}
};
// Expose context
setContext('TodoContext', { todos, filter, dispatch });
</script>
<slot />
Key Differences Table
Feature | React | Svelte 5 |
---|---|---|
Boilerplate | ~30 lines per context | ~15 lines per context |
Reactivity | Manual re-renders | Automatic via $state |
Provider Components | Required | Optional |
Type Safety | PropTypes/TypeScript | Built-in TypeScript |
State Updates | Immutable required | Direct mutation allowed |
Bundle Size (approx) | +40KB (runtime) | 0KB runtime |
Pro Tip: TypeScript Implementation
React + TypeScript:
interface Todo {
id: string;
text: string;
completed: boolean;
}
type TodoAction =
| { type: 'ADD_TODO'; payload: string }
| { type: 'TOGGLE_TODO'; payload: string };
Svelte 5 + TypeScript:
<script lang="ts">
import { setContext } from 'svelte';
type Todo = { /* same as above */ };
type TodoAction = { /* same as above */ };
// Annotate state directly
const todos = $state<Todo[]>([]);
const dispatch = $state<(action: TodoAction) => void>();
</script>
Alhamdulillah, Comparison Complete! 🎉
Why Choose Svelte 5?
- 60% less code for same functionality
- Direct DOM updates (no virtual DOM diffing)
- Gradual adoption (mix with other state solutions)
When to Use React Context:
- Existing React codebases
- Large teams familiar with Flux pattern
- Need error boundaries (Svelte equivalent coming in 5.1)
FAQ: Q: Can I use Redux with Svelte? A: Yes, but try native stores first - you might not need it!
Q: How to handle async actions? A: Same as React - use async/await in your dispatch functions
Saw a mistake? Edit on GitHub | Built with ❤️ in Indonesia