Oct 28, 2024

Universal reactivity with Svelte 5 runes

Svelte
Universal reactivity with Svelte 5 runes

After almost 18 months of development, comprising thousands of commits from dozens of contributors, Svelte 5 is finally stable.

Read the full blog

Svelte got a lot better. RUNES are just awesome.
Let's see how we can create a reusable reactive logic for managing global state.
Previously, we used stores, which had a different API and behavior, that created a disconnect and inconsistencies.

Now we can use runes in .svelte.ts or .svelte.js files. Define a shared reusable reactive logic with runes and access from any .svelte component. This eliminates prop drilling, reduce boilerplate, simplify store setup and better maintainability.

#. Example case

Let's take a look at sample code, a dark/light theme management.
Here, I created a color_scheme.svelte.ts file inside /lib/stores/.

color_scheme.svelte.ts
type ColorScheme = 'light' | 'dark'; let color_scheme = $state<ColorScheme>(get_initial_color_scheme());

This color_scheme is our main state here, this can be accessed from any component and they get updated whenever this state value changes.

But we can't export state from here, it will show error. Instead, we need a function which returns the value of this state and export that function instead.

color_scheme.svelte.ts
export function get_color_scheme() { return color_scheme; }

We can just import this function in any component and it will just work.
Now comes the interesting part, what if we also want to do something when this state value changes? Yes, we can use another rune called $effect in this same file.

color_scheme.svelte.ts
$effect.root(() => { $effect(() => { // get previous color scheme from localStorage const prev_color_scheme = get_localstorage_item('color_scheme'); // add new color_scheme and remove prev_color_scheme document.documentElement.classList.add(String(color_scheme)); if (prev_color_scheme !== color_scheme) document.documentElement.classList.remove(String(prev_color_scheme)); // update with new color_scheme set_localstorage_item('color_scheme', String(color_scheme)); }); });

(Ignore the logic inside it), Now whatever logic inside that $effect will just run whenever color_scheme changes.

But why $effect is wrapped inside another rune $effect.root, since this file is not a Svelte component, we can't just use $effect. $effect.root allows us to use nested effects outside of component initialization phase.
Official docs about $effect.root

We can use this in a random component like:

svelte
<script lang="ts"> import { get_color_scheme, toggle_color_scheme } from '$lib/stores/color_scheme.svelte'; </script> <button onclick={toggle_color_scheme}>{get_color_scheme()}</button>

Full code:

color_scheme.svelte.ts
import { get_localstorage_item, set_localstorage_item } from '$lib/functions/localstorage'; type ColorScheme = 'light' | 'dark'; let color_scheme = $state<ColorScheme>(get_initial_color_scheme()); function get_initial_color_scheme() { // dark mode if: // localStorage has dark or there is no scheme on localStorage and user set mode to dark // else: light mode if ( (get_localstorage_item('color_scheme') as ColorScheme) === 'dark' || (get_localstorage_item('color_scheme') === null && typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches) ) { return 'dark'; } else { return 'light'; } } $effect.root(() => { $effect(() => { // get previous color scheme from localStorage const prev_color_scheme = get_localstorage_item('color_scheme'); // add new color_scheme and remove prev_color_scheme document.documentElement.classList.add(String(color_scheme)); if (prev_color_scheme !== color_scheme) document.documentElement.classList.remove(String(prev_color_scheme)); // update with new color_scheme set_localstorage_item('color_scheme', String(color_scheme)); }); }); export function get_color_scheme() { return color_scheme; } export function change_color_scheme(scheme: ColorScheme) { color_scheme = scheme; } export function toggle_color_scheme() { if (color_scheme === 'dark') color_scheme = 'light'; else color_scheme = 'dark'; }

#. Helpful links