sveltekit: component is not being rendered second time though the URL has been changed

Viewed 1583

I am a newbie to sveltekit.

I have a page [symbol].svelte.

http://localhost:3000/stocks/mnr-pc = host/stocks/[symbol]

While the symbol of the URL gets changed, it should get that symbol's data from API and pass the data as props to the child component.

To change the slug/symbol, I have a search box at the top of the page. I search for a symbol, while I click on any symbol from the search box, I change the url.

For example: if I select the symbol 'tsla', I change the URL to http://localhost:3000/stocks/tsla.

The problem is, at the first time when the URL is changed, it fetches data from API, and displays the data through the child component. But after that, if I search for a new symbol again and click on it, though the router gets changed, but still the data on the UI remains the same.

[symbol].svelte this file is in routes/stocks folder

<script context="module">
    import StockSymbol from '$lib/stocks/index.svelte';
    import data from '../../../static/symbols.json';

    let props;
    let symbolName;
    export const hydrate = true

    export async function load({ page, fetch }) {
        symbolName = page.params.symbol;
        let symbols = data.symbols;
        let symbolFound = symbols.find(s => s.s.toLowerCase() === page.params.symbol.toLowerCase());

        if (!symbolFound) {
            return {
                status: 302,
                redirect: '/404'
            }
        }

        const url = `APIHOST/${symbolName}`;
        const res = await fetch(url);
        let stock = await res.json();
        stock = stock.data.attributes;

        props = {
            stock
        };

        return {
            props: {
                stock
            }
        }
    }
</script>

<svelte:head>
    <title>Stock: { symbolName || ''}</title>
</svelte:head>

<svelte:component this={StockSymbol} {...props}/>

search.svelte

<script context="module">
    import AutoComplete from "simple-svelte-autocomplete";
    import data from '../../../static/symbols.json';
    import { goto, prefetch } from '$app/navigation';

    var options = data;
    let selectedOption;

    const handleOnChange = () => {
        console.log(selectedOption);
        if (selectedOption) {
            goto(`/stocks/${selectedOption.s.toLowerCase()}`, {replaceState: true});
        }
    }
</script>

<div class="min-w-0 hidden sm:block flex-1 md:px-8 lg:px-48 xl:col-span-6">
    <div class="flex items-center px-6 py-4 md:max-w-3xl md:mx-auto lg:max-w-none lg:mx-0 xl:px-0">
        <div class="w-full">
            <label for="search" class="sr-only">Search</label>
            <div class="relative">
                <div class="z-20 pointer-events-none absolute inset-y-1 left-0 pl-3 flex items-center">
                    <svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                        <path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"></path>
                    </svg>
                </div>
                <AutoComplete 
                    onChange={handleOnChange}
                    inputId="search" 
                    placeholder="Search" 
                    className="w-full text-md"
                    inputClassName="block px-10 py-2 w-full rounded-md border border-gray-300 text-sm placeholder-gray-500 focus:outline-none focus:text-gray-900 focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" 
                    items={data.symbols.map(o => o)} 
                    labelFunction={o => o.s + '. ' + o.n}
                    bind:selectedItem={selectedOption} 
                    hideArrow={true}
                    showClear={true}
                    showLoadingIndicator={true}
                    noResultsText="No Results Found"
                    maxItemsToShowInList="10"
                    localFiltering=false 
                />
            </div>
        </div>
    </div>
</div>

svelte.config.js

import vercel from '@sveltejs/adapter-vercel';

/** @type {import('@sveltejs/kit').Config} */
const config = {
    kit: {
        target: '#app',
        adapter: vercel(),
        hydrate: true,
        vite: {
            build: {
                minify: true,
                brotliSize: true,
                sourcemap: true,
            }
        }
    }
};

export default config;

So here how it works

Please help me here. Thanks.

1 Answers

You declare props in the module script and assign it inside the load function. This works only the first time. Svelte explicitely states in its docs that variable updates inside the module script are not reactive. The way to solve is to either explicitely declare the props (the prefered way) or use $$restProps:

<script context="module">
    //...
    // Do NOT declare "let props" here

    export async function load({ page, fetch }) {
        // ..

        return {
            props: {
                stock // <- handed to the instance script
            }
        }
    }
</script>

<script>
    export let stock; // <- this gets the stock from "return { props: { stock } }" above
</script>
..
<svelte:component this={StockSymbol} {stock}/>
<!-- or (then you don't need the "export let stock" abive) -->
<svelte:component this={StockSymbol} {...$$restProps}/>
Related