Svelte 5 introduces a powerful new reactive primitive system called "runes" that simplifies state management. Let's explore what runes are and dive deep into two important ones: $derived
and $effect
To demonstrate how to use these runes we will use a water tracker application that I've built. You can find the source code here: https://github.com/mikhail-karan/water-tracker-svelte5
What are Svelte 5 Runes?
Runes are special primitives in Svelte 5 marked with a $
prefix that enable fine-grained reactivity. Unlike Svelte 4's compiler-based reactivity, runes bring reactivity directly into the JavaScript runtime, making it more explicit and portable.
The main runes include:
$state
- Declares reactive state$derived
- Computes values from state$effect
- Runs side effects when dependencies change$props
- Handles component props
The Power of $derived
$derived
is used to compute values that depend on reactive state. It automatically updates whenever its dependencies change, and importantly, it's memoized (calculated only when needed).
From our water tracker app:
1const blocksToFill = $derived(Math.min(Math.floor(waterConsumed / 100), 20));
2const waterBlocks = $derived(Array(20)
3 .fill(false)
4 .map((_, index) => index < blocksToFill)); // 20 blocks representing 100ml each
5const lastDrinkDerived = $derived(waterConsumed > 0 ? new Date() : null);
Here we use $derived
to:
- Calculate how many water blocks to fill based on water consumed
- Generate an array of boolean values representing filled/unfilled blocks
- Track when the last drink was taken
These values are perfect for $derived
because they:
- Are pure calculations based on state
- Need to update automatically when dependencies change
- Don't perform side effects
Understanding $effect
$effect
is for running side effects when reactive values change. Unlike $derived
, it doesn't return a value - it performs actions.
From our app:
1$effect(() => {
2 // Persist data to localStorage whenever values change
3 if (!firstRun) {
4 localStorage.setItem(
5 'waterData',
6 JSON.stringify({
7 waterConsumedSaved: waterConsumed,
8 lastDrinkSaved: lastDrink,
9 dailyGoalSaved: dailyGoal
10 })
11 );
12 } else {
13 const waterData = localStorage.getItem('waterData');
14 if (waterData) {
15 const { waterConsumedSaved, lastDrinkSaved, dailyGoalSaved } = JSON.parse(waterData);
16 waterConsumed = waterConsumedSaved;
17 lastDrink = lastDrinkSaved ? new Date(lastDrinkSaved) : null;
18 dailyGoal = dailyGoalSaved;
19 }
20 firstRun = false;
21 }
22});
This $effect
is perfect for what it's doing:
- Persisting data to localStorage (side effect)
- Loading data on first run (side effect)
- Tracking when state changes to trigger these operations
When to Use $effect (Sparingly)
$effect
should be used rarely and only for side effects. Common appropriate uses include:
- Persisting data to localStorage (as in our example)
- Making API calls when dependencies change
- Interacting with the DOM in ways not handled by Svelte's templating
- Logging or analytics
Common $effect Misuses
Here are examples of misusing $effect
that should be avoided:
Misuse 1: Computing values that should be $derived
// BAD
let doubledWater = 0;
$effect(() => {
doubledWater = waterConsumed * 2;
});
// GOOD
const doubledWater = $derived(waterConsumed * 2);
Misuse 2: Conditional side effects that could be inline
// BAD
$effect(() => {
if (waterConsumed >= dailyGoal) {
console.log('Goal reached!');
}
});
// GOOD - In template
{#if waterConsumed >= dailyGoal}
<div on:mount={() => console.log('Goal reached!')}>Goal reached!</div>
{/if}
Misuse 3: Overusing for simple DOM manipulations
$effect(() => {
document.title = `Water: ${waterConsumed}ml`;
});
// GOOD - Use Svelte's <svelte:head> component instead
Conclusion
Understanding the difference between $derived
and $effect
is crucial:
$derived
is for computing values based on state (pure, returns a value)$effect
is for running side effects when state changes (impure, no return value)
By using the right rune for the job, your Svelte 5 applications will be more maintainable, predictable, and efficient. Our water tracker app demonstrates how these runes can work together to create a responsive, state-driven application.