This is how I used to do it in Svelte 4:
// $lib/firebase.ts
export function writableRealtimeStore<T>() {
let unsubscribe: () => void = () => {}
let objectRef: any
const store = writable<T | null>(null)
let storeSet = store.set
return {
subscribe: store.subscribe,
set: (value: any) => {
return set(objectRef, value)
},
update: () => {},
setPath: (path: string) => {
objectRef = ref(realtimeDB, path)
unsubscribe()
unsubscribe = onValue(objectRef, (snapshot) => {
storeSet((snapshot.val() as T) ?? null)
})
},
}
}
// $lib/stores.ts
export const myStore = writableRealtimeStore()
// routes/+page.svelte
<script lang="ts">
import { myStore } from '$lib/stores'
myStore.setPath('/books/<book_id>')
</script>
<input type="text" bind:value={myStore.bookName}
This store is reactive both ways – when the value in the DB changes, it updates the UI, and when the user updates the value of the input, the DB changes. I could access the properties of my DB object directly as myStore.bookName
.
However with Svelte 5 I can’t get the same behavior of the store object:
// $lib/firebase.ts
export function createRealtimeStore<T>() {
let unsubscribe = () => {}
let store: { value: T | undefined } = $state({ value: undefined })
let _ref: DatabaseReference
return {
get value(): T | undefined {
return store.value
},
update: () => {
if (_ref) set(_ref, store.value)
},
setPath: (path: string) => {
_ref = ref(realtime, path)
unsubscribe()
unsubscribe = onValue(_ref, (snapshot) => {
store.value = snapshot.val()
})
},
unsubscribe,
}
}
// $lib/stores.ts
export let myStore = createRealtimeStore()
// routes/+page.svelte
<script lang="ts">
import { myStore } from '$lib/stores'
myStore.setPath('/books/<book_id>')
$effect(() => {
if (project.value) {
project.update()
}
})
</script>
<input type="text" bind:value={myStore.value.bookName}
Two problems:
- I must access the store’s props like
myStore.value.bookName
, instead of the cleanermyStore.bookName
. - The
$effect
rune must be in the page and not in the function that creates the store, because$effect
can only be called during component initialization, otherwise you get an error.
Overall the Svelte 4 way of doing it was much cleaner and nice to work with and I refuse to believe that you can’t do the same thing with the new and supposedly improved store system.