Search code examples
sveltesvelte-5

Svelte 5 - change component on login


I am trying to change component once the user is logged in, but so far I am not able to do this. Once I make successful login, component remains the same.

Link to the test repo

I have App.svelte component:

<script>

    import { fade } from 'svelte/transition'
    import { currentComponent } from './store/view.svelte';

    const loadComponent = currentComponent()

    $effect(() => {
        console.log(loadComponent);
    })

</script>


<div id="viewport" transition:fade>
    <svelte:component this={loadComponent.component}></svelte:component>
</div>

Login.svelte component

<script>
    import { currentComponent } from "../store/view.svelte";

    let email = $state('')
    let password = $state('')

    let test = currentComponent()

    /**
    * @param {{ preventDefault: () => void; }} event
    */
    async function submit(event){
        event.preventDefault()

        fetch('http://localhost:9555/api/login', {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json; charset=UTF-8',
            },
            body: JSON.stringify({
                email,
                password
            })
        })
            .then(resp => resp.json())
            .then(json => {
                localStorage.setItem('_token', json.token)

                test.updateComponent()
            })
    }
</script>
<div
        class="relative mx-auto w-full max-w-md bg-white px-6 pt-10 pb-8 shadow-xl ring-1 ring-gray-900/5 sm:rounded-xl sm:px-10">
    <div class="w-full">
        <div class="text-center">
            <h1 class="text-3xl font-semibold text-gray-900">Sign in</h1>
            <p class="mt-2 text-gray-500">Sign in below to access your account</p>
        </div>
        <div class="mt-5">
            <form action="" onsubmit="{submit}">
                <div class="relative mt-6">
                    <input
                            bind:value={email}
                            placeholder="Email Address"
                            class="peer mt-1 w-full border-b-2 border-gray-300 px-0 py-1 placeholder:text-transparent focus:border-gray-500 focus:outline-none"
                            autocomplete="NA" />
                    <label for="email" class="pointer-events-none absolute top-0 left-0 origin-left -translate-y-1/2 transform text-sm text-gray-800 opacity-75 transition-all duration-100 ease-in-out peer-placeholder-shown:top-1/2 peer-placeholder-shown:text-base peer-placeholder-shown:text-gray-500 peer-focus:top-0 peer-focus:pl-0 peer-focus:text-sm peer-focus:text-gray-800">Email Address</label>
                </div>
                <div class="relative mt-6">
                    <input type="password" name="password" id="password"
                            bind:value={password}
                            placeholder="Password" class="peer peer mt-1 w-full border-b-2 border-gray-300 px-0 py-1 placeholder:text-transparent focus:border-gray-500 focus:outline-none" />
                    <label for="password" class="pointer-events-none absolute top-0 left-0 origin-left -translate-y-1/2 transform text-sm text-gray-800 opacity-75 transition-all duration-100 ease-in-out peer-placeholder-shown:top-1/2 peer-placeholder-shown:text-base peer-placeholder-shown:text-gray-500 peer-focus:top-0 peer-focus:pl-0 peer-focus:text-sm peer-focus:text-gray-800">Password</label>
                </div>
                <div class="my-6">
                    <button type="submit" class="w-full rounded-md bg-black px-3 py-4 text-white focus:bg-gray-600 focus:outline-none">Sign in</button>
                </div>
                <p class="text-center text-sm text-gray-500">Don&#x27;t have an account yet?
                    <a href="#!"
                        class="font-semibold text-gray-600 hover:underline focus:text-gray-800 focus:outline-none">Sign
                        up
                    </a>.
                </p>
            </form>
        </div>
    </div>
</div>

view.svelte.js:

import Restaurant from "../routes/Restaurant.svelte";
import Login from '../routes/Login.svelte';

export function currentComponent(){

    const _token = localStorage.getItem('_token')

    let authorized = $state(_token ? true : false)

    let component = $derived(!authorized ? Login : Restaurant)

    function updateComponent(){
        authorized = true
    }

    return {
        get authorized(){
            return authorized
        },
        set authorized(newValue){
            authorized = newValue
        },
        get component(){
            return component
        },
        updateComponent
    }

}

Once I update authorized (test.updateComponent() call), the component is updated in view.svelte.js but the new component is not rendered. What am I doing wrong here?


Solution

  • You are not changing the same instance, hence nothing happens.

    You could e.g. do either of

    • Make currentComponent a singleton, e.g.

      function createCurrentComponent() {
        // [old code]
      }
      
      export const currentComponent = createCurrentComponent();
      

      (Of course you then would not call currentComponent any more.)

    • Create one instance at the root component and pass that around, e.g. via a context.

    Also, for the fade to work, you would need something like this:

    <div class="stack">
        {#key loadComponent.component}
            <div id="viewport" transition:fade>
                <svelte:component this={loadComponent.component} />
            </div>
        {/key}
    </div>
    
    <style>
        .stack { display: grid }
        .stack > * { grid-area: 1 / 1 }
    </style>