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.
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. It can be done with Runes as well.
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
const user = useRune('user', { ...user state });
Component 2
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.
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:
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 article in the coming weeks.
J
See Also:
- Code.Build - Finishing Firebase Course
- Newsletter - Subscribe for more Svelte 5 and Firebase tips!