Using Sharable Runes with TypeScript in Svelte5
Tags: svelte, sveltekit, webdev
Published: 2024-04-28 21:42:23

I don't know about you, but I don't like using Runes with `$state` in Svelte 5. Sure, they're easier to set data in your component, but I don't write code and manually put getters and setters.

## Single Responsibility Principle
I don't write state inside my component except in small apps. I like to create *reusable shared hooks*. If it can't be shared, it is still better to follow the SRP for clean coding techniques.

## rune.svelte.ts
First, I created a rune that works like Nuxt or Qwik Signals. I don't want to call the variable as a function, and I don't want to call set. The `value` attribute is the best implementation. You can create your own if you disagree.
```ts
export const rune = <T>(initialValue: T) => {

    let _rune = $state(initialValue);

    return {
        get value() {
            return _rune;
        },
        set value(v: T) {
            _rune = v;
        }
    };
};
```
This is what I use instead of $state everywhere in my app, with the exception of small changes in a component.

## Shared Store
If you follow my posts, than you've seen a version of my [shared store](https://dev.to/jdgamble555/the-correct-way-to-use-stores-in-sveltekit-3h6i). It can be done with Runes as well.
```ts
import { getContext, hasContext, setContext } from "svelte";
import { readable, writable } from "svelte/store";
import { rune } from "./rune.svelte";

export const useSharedStore = <T, A>(
    name: string,
    fn: (value?: A) => T,
    defaultValue?: A,
) => {
    if (hasContext(name)) {
        return getContext<T>(name);
    }
    const _value = fn(defaultValue);
    setContext(name, _value);
    return _value;
};

// writable store context
export const useWritable = <T>(name: string, value?: T) =>
    useSharedStore(name, writable, value);

// readable store context
export const useReadable = <T>(name: string, value: T) =>
    useSharedStore(name, readable, value);

// shared rune
export const useRune = <T>(name: string, value: T) =>
    useSharedStore(name, rune, value);
```
Using this method, you can call `useRune` in you app for shared state anywhere.
### Component 1
```ts
const user = useRune('user', { ...user state });
```
### Component 2
```ts
const user = useRune('user');
```
And it will just work!

## Custom Runes
You can do the same thing with custom Runes. Let's say I want to keep track of the Firebase user's state, and I want to share it across my app. I don't want to keep calling `onIdTokenChanged`. I can simply created a shared hook.

```ts
const _useUser = (defaultUser: UserType | null = null) => {

    const user = rune(defaultUser);

    const unsubscribe = onIdTokenChanged(
        auth,
        (_user: User | null) => {
            if (!_user) {
                user.value = null;
                return;
            }
            const { displayName, photoURL, uid, email } = _user;
            user.value = { displayName, photoURL, uid, email };
        });

    onDestroy(unsubscribe);

    return user;
};

export const useUser = (defaultUser: UserType | null = null) =>
    useSharedStore('user', _useUser, defaultUser);
```
Now I can use:
```ts
const user = useUser();
```
Anywhere in my app (hooks or components!), and is **SAFE** for the server. I believe this should be built into Svelte (and all Frameworks). The closest thing I have seen is `useState()` in Nuxt --- not to be confused with React.

Hope this helps those that are migrating to Svelte 5. I will be updating my [SvelteKit Firebase Todo App](https://code.build/p/sveltekit-todo-app-with-firebase-C73xaa) article in the coming weeks.

J

See Also:
- [Code.Build](https://code.build) - Finishing Firebase Course
- [Newsletter](https://code.build/newsletter) - Subscribe for more Svelte 5 and Firebase tips!