Typed Svelte store with initial value from local storage

161 views Asked by At

I'm trying to implement JWT authentication in my SvelteKit SPA. I'm new to TS (and thus Svelte[Kit]) and there is a lot to learn, so my problem might not strictly be with Svelte and its store system but also with TS in general.

My root +layout.svelte for all routes that require authentication looks like this:

<script lang="ts">
    import { goto } from '$app/navigation';
    import auth from '$lib/stores/auth';
</script>

{#await auth.init() then user}
    <slot />
{:catch}
    {goto('/login')}
{/await}

lib/stores/auth.ts:

import { writable, get } from 'svelte/store';
import { jwtDecode, type JwtPayload } from 'jwt-decode';

type User = {
    email: string;
    name: string;
};

interface Token extends JwtPayload {
    user: User;
}

function createStore() {
    const user = {};
    const { subscribe, update, set } = writable(user as User);

    return {
        subscribe,

        async init() {
            const tokenStr = localStorage.getItem('token')!;
            const token = jwtDecode<Token>(tokenStr);
            set(token.user);
        },

        setToken(tokenStr: string) {
            const token = jwtDecode<Token>(tokenStr);
            let storeValue = get(this);

            storeValue = token.user;

            update(() => storeValue);

            localStorage.setItem('token', tokenStr);
        }
    };
}

export default createStore();

My problem is that I can't use the user result object in my layout (e.g. to display the username in the navigation, etc.) It is undefined. Why is that?

2

There are 2 answers

0
LuMa On BEST ANSWER

So I ended up using svelte-persisted-store as also suggested by @José.

lib/stores/auth.ts:

import { derived, writable } from 'svelte/store';
import { browser } from '$app/environment';
import { jwtDecode, type JwtPayload } from 'jwt-decode';

type User = {
    email: string;
    name: string;
};

interface Token extends JwtPayload {
    user: User;
}

export const token = (() => {
    const { set, subscribe, update } = writable(browser ? localStorage.getItem('token') : null);
    return {
        set: (value: string | null) => {
            value ? localStorage.setItem('token', value) : localStorage.removeItem('token');
            set(value);
        },
        subscribe,
        update
    };
})();
export const user = derived(token, (token) => (token ? jwtDecode<Token>(token).user : null));

layout.svelte:

<script lang="ts">
    import { goto } from '$app/navigation';
    import { user } from '$lib/stores/auth';
</script>

{#if $user}
    <slot />
{:else}
    {goto('/login')}
{/if}
1
Yilmaz On

the +layout.svelte file is a component that can render both on the server and the client. you are using localstorage which is a client-specific api. Therefore, attempting to access localStorage within the +layout.svelte file during server-side rendering would result in user being undefined.

in layout file, use

export const ssr = false;