DEV Community

Jonathan Gamble
Jonathan Gamble

Posted on

4 1 1 1

Sharing Runes in Svelte 5 with the Rune Class

I generally don't use classes in any TypeScript I write. I believe functions make things simpler, and you no longer get Tree Shaking with methods in a class.

However, using classes with Runes can actually be faster, because they don't have to compile $state variables with get and set or with value... they just work. This is what Rich Harris recommends in many cases.

Sharable Rune

We need a sharable Rune class, and of course we must use Context.

// rune.svelte.ts
import { getContext, hasContext, setContext } from "svelte";


type RCurrent<TValue> = { current: TValue };

export class Rune<TRune> {

    readonly #key: symbol;

    constructor(name: string) {
        this.#key = Symbol(name);
    }

    exists(): boolean {
        return hasContext(this.#key);
    }

    get(): RCurrent<TRune> {
        return getContext(this.#key);
    }

    init(value: TRune): RCurrent<TRune> {
        const _value = $state({ current: value });
        return setContext(this.#key, _value);
    }

    update(getter: () => TRune): void {
        const context = this.get();
        $effect(() => {
            context.current = getter();
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

And we can export custom runes where we want.

// counter.svelte.ts
import { Rune } from "./rune.svelte";

export const counter = new Rune<number>('counter');
Enter fullscreen mode Exit fullscreen mode

This is how most people share $state variables anyway, however, this is safe for the server (see my previous posts in this thread). We must name it like any other context.

Initialize

We must initialize our $state just like anywhere else, only once, in the parent component.

<script lang="ts">
    import { counter } from '$lib/counter.svelte';

    const count = counter.init(0);
</script>
Enter fullscreen mode Exit fullscreen mode

Read Anywhere

We can use it safely in a child component and read the current method.

<script lang="ts">
    import { counter } from '$lib/counter.svelte';

    const count = counter.get();
</script>

<h1>Hello from Child: {count.current}</h1>

<button type="button" onclick={() => count.current++}>
  Increment From Child
</button>
Enter fullscreen mode Exit fullscreen mode

Reactively Update Anywhere

To update a shared $state, we must pass it through a function that can get called.

<script lang="ts">
    import { counter } from '$lib/counter.svelte';

    let value = $state(8);

    counter.update(() => value);
</script>
Enter fullscreen mode Exit fullscreen mode

This allows the update to be reactive. We can pass any signal or store variable, and it will update just like $derived.

Update Normally

Of course, you can update normally as well.

<script lang="ts">
    import { counter } from '$lib/counter.svelte';

    const count = counter.get();

    count.current = 9;
</script>
Enter fullscreen mode Exit fullscreen mode

I hope you find value,

J

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay