javascriptsveltesveltekitsvelte-store

Creating a writable svelte store from loaded page data in sveltekit


Using sveltekit with svelte 4, There's data required to server side render pages that is loaded in the root +layout.server.js. The data is then used both on the client and server side, however, I need a way to edit the data after the page is loaded and the $page store is readonly. If I import a store and .set() it inside the root +layout.svelte, then all users/requests will trigger updates to the store on the server side, which is bad.

So how do I go about creating a store, or simple object, that is instantiated in the root server layout, which can be edited on the client side later?

I tried to use depends('data:rates') but that makes all the nested routes rerun their load functions and it still doesn't let me have fine-grained control on the data and timing from the client.

Note that a store limited to one file would work, but this store/object needs to be imported by dozens of locations across the site.

Thanks.


Solution

  • Assuming your +layout.server.js looks something like this:

    export const load = async ({ fetch }) => {
      const response = await fetch(`/api/rates`);
      const rates = await response.json();
      
      return { rates: rates }
    }
    

    Two ways to solve this problem in my experience:

    1. Create a store in +layout.js

    In an adjacent client-side +layout.js, return a writable:

    import { writable } from 'svelte/store';
    
    export const load = async ({ parent, data }) => {
        return {
            ...(await parent()),
            ...data,
            rates: writable(data.rates),
        };
    };
    

    Child pages can use the store with data.rates

    <script>
      // +page.svelte
      export let data;
    
      $: rates = data.rates;
    
      // or; in other components
      // import { page } from '$app/stores'
    
      // $: rates = $page.data.rates;
    
      async function refreshRates = () => {
        $rates = await fetch(`/api/rates`);
      }
    </script>
    
    <button on:click={refreshRates}>Refresh</button>
    

    2. Create a store in +layout.svelte

    This example is also given in the Svelte docs:

    <script>
      import { onMount, setContext } from 'svelte';
      import { writable } from 'svelte/store';
    
      export let data;
      const rates = writable(null);
    
      $: setContext('rates', rates);
      $: rates.set(data.rates);
    </script>
    
    <slot/>
    

    Then, in a child page/component:

    <script>
      import { setContext } from 'svelte';
    
      const rates = getContext('rates', rates);
    
      async function refreshRates = () => {
        $rates = await fetch(`/api/rates`);
      }
    </script>
    
    <button on:click={refreshRates}>Refresh</button>