Svelte Stores are amazing. They allow you to share data across different components, and keep them reactive. Everything is done automatically. This is completely unique from every other framework. There are no awkward providers that are parents of providers. Things just work with the Svelte Magic. Some people hate this, I personally love it.
That being said, this unfortunately is not the case in SvelteKit. The current prediction of the future of the web will need server components. SvelteKit, not Svelte, especially now that it is in 1.0, will be able to handle it. However, most tutorials are written for Svelte; I suspect we shall see a lot of poorly written code using stores in SvelteKit. Let's address the problem and how to fix it.
1. Do not use stores in endpoints, actions, or load functions
Stores will not work correctly on the server as is, so just don't use them on the server. You could technically make an exception for a load function where you check for the browser
environment, but in reality, if you have the correct configuration, everything you need should be available in the $page
variable. This will also prevent you from accidently declaring a global variable. Just don't do it. There is always a better way.
2. Do not use stores at all in isolated components
Technically you can do this, but there is no reason to. There are so many tutorials that show examples of this, but you don't need to. If you're not sharing the data across components, simple use the reactive declaration - $:. Declarative programming will keep you from having to manage subscriptions, and you don't need to worry about passing any props.
3. Do not pass a store as a parameter
This may seem controversial, but it makes sense if you think about it. If you're only using a store in a parent child component, see #2. Make the variables reactive or create an event dispatcher.
The Correct Way
According to Svelte Docs, the official way to handle this is to use setContext
and getContext
. However, we want to follow best practices, specifically the single responsibility principle. Any senior React engineer will know to separate hooks into their own component. Each store should follow the same principles.
First, create a reusable useReadable
and useWritable
component. This will handle the contexts for you so that you don't have to think about it.
use-shared-store.ts
import { getContext, hasContext, setContext } from "svelte";
import { readable, writable } from "svelte/store";
// context for any type of store
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);
Next, create your stores just like you would create a custom writable. The only difference is that you have to name your store with a string. Here we use dark
.
stores.ts
export const useDarkMode = () => useWritable('dark', false);
And finally, import the hook
to use your store in your component. Then you can use it just like any store.
<script lang="ts">
import { useDarkMode } from 'stores.ts';
...
const darkMode = useDarkMode();
</script>
{#if $darkMode}
// do something
{/if}
The beauty of this is that that you don't have to think about context at all; you just import your hook.
And Custom Stores... ?
Follow my previous post for custom stores. Here you can just use it like any other type of store.
export const jokerStore = (value: string) = {
const { set, update, subscribe } = writable<string | null>(value);
return {
set,
update,
subscribe
setJoker: () => set('joker')
}
};
export const useJoker = () =>
useSharedStore('joker-store', jokerStore, 'harley');
Siblings
I should add that if you share a store between two sibling components, you will have to declare the hook in the parent component. You can simply do:
useMyHook();
This will set the context automatically under-the-hood to a higher level for shareability.
Final Thoughts
I am a firm believer that this should be built into SvelteKit and we should not have to think about it. There will unfortunately be a lot of data leaks because of coders not being aware that a global variable can be shared. Svelte Team, Please make this happen! There are a handle full of issues regarding this. There are also over-complicated work-arounds and external packages (I kind of like the Map version), but... as a Mandalorian will tell you.
This is the way...
Check out code.build for more tips. I am currently rebuilding it with Tailwind...
J