Home > Software design >  How do I set the type, for custom store, when I pass store via setContext or component prop?
How do I set the type, for custom store, when I pass store via setContext or component prop?

Time:08-31

When using SvelteKit, the store must always be created in the route page/layout, otherwise there will be problems with the status being up-to-date after refreshing.
You can't create a store in an imported module, because the server-side module only loads once, and this causes errors in the store.
All this leads to a certain situation with types.

The problem:

I create and pass the store from page.svelte to Child.svelte, via setContext:


<!--  page.svelte -->
<script>
  import Child from "./Child.svelte";
  import { writable } from 'svelte/store';
  import { setContext } from "svelte";
  
  function createCount() {
    /** @type {import("svelte/store").Writable<number>} */
    const { subscribe, set, update } = writable(0);
    
    return {
      subscribe,
      increment: () => update(n => n   1),
      decrement: () => update(n => n - 1),
      reset: () => set(0)
    };
  }
  
  export const count = createCount();
  
  setContext("store", count);
</script>

<Child/>
<!-- Child.svelte -->
<script>
import { getContext } from "svelte";
  
  const count = getContext("store");
</script>

<h1>The count is {$count}</h1>

<button on:click={count.increment}> </button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>

In Child.svelte the type for store count is any:
obraz

And it should be as in the place of the declaration:
obraz

That's what it should be the type of store, returned from getContext("store"):

const count: {
    subscribe: (this: void, run: Subscriber<number>, invalidate?: Invalidator<number> | undefined) => Unsubscriber;
    increment: () => void;
    decrement: () => void;
    reset: () => void;
}

Passing via <Child {count}/> has the same problem.
obraz

QUESTION: How can this be done?

Alternative

It doesn't. You can only create a store in a module, and import it - then the types are preserved.
But this creates some problems with store validity, as I wrote about at the beginning.

CodePudding user response:

There are two primary options: Use preprocess-svelte and write TypeScript or add JSDoc type annotations.

With TS:

<script lang="ts">
    import { getContext } from 'svelte';

    // Prop
    export let count: StoreType;

    // Context
    const store = getContext<StoreType>('context');
</script>

With JSDoc:

<script>
    import { getContext } from 'svelte';

    // Prop
    /** @type {StoreType} */
    export let count;

    // Context
    /** @type {StoreType} */
    const store = getContext('context');
</script>

You can declare types in separate .d.ts files or use @typedef.


The createCount function can be extracted to a separate file without issue. That way its return type can also be used when typing props/contexts. E.g.

// create-count.js
import { writable } from 'svelte/store';

export function createCount() {
    const { subscribe, set, update } = writable(0);

    return {
        subscribe,
        increment: () => update(n => n   1),
        decrement: () => update(n => n - 1),
        reset: () => set(0),
    };
}
<script>
    import { getContext } from 'svelte';

    /** @type {ReturnType<import('./create-count').createCount>} */
    export let count;

    /** @type {ReturnType<import('./create-count').createCount>} */
    const store = getContext('context');
</script>

In TS this is cleaner, since you can just import the type as part of regular code and use type aliases.

<script lang="ts">
    import type { createCount } from './create-count';
    import { getContext } from 'svelte';

    export let count: Count;

    const store = getContext<Count>('context');

    type Count = ReturnType<typeof createCount>;
</script>
  • Related