Jonathan Gamble
Posted on April 28, 2024
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.
Update 11/15/24
You can also use this version for simplification. You MUST create a variable for this to work and return that variable for $state
to compile correctly.
export const rune = <T>(initialValue: T) => {
const _rune = $state({ value: initialValue });
return _rune;
};
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!
Posted on April 28, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
August 21, 2024