The Correct Way to Use Stores in SvelteKit
Jonathan Gamble

Jonathan Gamble @jdgamble555

About: My main job is not in IT, but I do have a CS degree and have been programming for over 20 years (only born in 1984). I hate dealing with Servers, and want Graph Databases to be the new norm.

Location:
Louisiana
Joined:
Feb 9, 2021

The Correct Way to Use Stores in SvelteKit

Publish Date: May 5 '23
43 10

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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

Comments 10 total

  • Brendan Matkin
    Brendan MatkinMay 8, 2023

    This is an interesting solution! I agree that something like this should just be built in. Gonna try this soon!

    I also just wrote something up that's a little higher level explanation of what's going on. I don't have any novel fixes, just some clarification and summary of what's missing from the docs: dev.to/brendanmatkin/safe-svelteki...

  • jrmoynihan
    jrmoynihanJun 8, 2023

    Your point about not needing global state when local will do is great.

    That being sad, your point in #2 has some exceptions. Isolated components might need to make use of certain stores, like tweened and spring because they already provide specific behaviors out of the box.

    I also like the idea to use stores + contexts, but I found this implementation a little hard to follow. Part of it is probably the nomenclature you used in the post. These aren't "hooks", at least in Svelte's definition of them, even though they provide similar functionality to React hooks. The "useWritable" semantics also feel weird -- if I wanted to use writable(), I'd just import it! What does "use" tell you about what the function does?

    As a comparison, I wrote a similar type of convenience function for stores + contexts a while back with some more explicit typing, even for the generics, but I also stuck with the "get/set" naming convention Svelte uses internally for contexts. I think this makes it much more clear to the end-user what is actually being done here. You could still make a getDarkMode() function if you were doing that context lookup a lot and wanted a quick alias for getWritableContext('dark').

    One of the problems with context that Rich brings up in the tutorial, is using unique keys within a context tree. I don't think your implementation can deal with that as-written. Symbol can solve that, as he points out.

    Lastly, I'm curious why you chose to put the getContext() and setContext() within the same function, useSharedStore? That feels a little risky to me. Isn't it important to know that a context a child component is trying to get wasn't set above in its parent tree? The child component shouldn't be doing the setting here if the provided key fails the if(hasContext(name)) check!

    I don't always get the single-responsibility principle correct in my own code either, but this seems like it would be substantially improved just by being more explicit, and splitting up the functionality.

    • Jonathan Gamble
      Jonathan GambleJun 9, 2023

      What is tweened and sprint? I think I agree there are exceptions, but generally people could avoid stores for reactive statements.

  • Jonathan Gamble
    Jonathan GambleAug 11, 2023

    Hi Joe. What exactly is "awful?" I would be glad to update something if it is unclear. The SvelteKit docs just say to use context, but don't mention the gotchas that I do etc.

  • Terris Linenbach
    Terris LinenbachSep 4, 2023

    use is used by Svelte actions, so the prefix use doesn't look like a React hook in Svelte code, which would only look familiar to React developers. Use plain get instead if you need a prefix. Otherwise, there are good points here. I'm stealing useSharedStore (with a different name..)

  • Alan Daniel
    Alan DanielOct 28, 2023

    I'd like to know what are your throughts about signals, because i'd hate to write a lot of update(self => x = y, z = 1, return self) every time i just want to update a parameter.. I'm considering on waiting for svelte 5

  • John Hamman
    John HammanDec 19, 2023

    How would you use "derived" with all this?

  • Zxce3
    Zxce3Jun 27, 2024

    Nicee

  • Pick Avana
    Pick AvanaAug 6, 2024

    can you revisit this for Svelte 5.0?

Add comment