Search code examples
sveltesveltekit

Exposing a property loaded from the data in an #await block to a parent Component


I have a component which needs to wait for an async function, loadSvx to load data from an .svx file. I used an #await block for this, and :then store the result in svx.

svx.meta then holds the properties of the 'Front Matter' listed in the .svx file. which I want to pass to the Parent component to display, i.e., the title of this .svx file.

How can I assign my export let frontMatter the data from svx.meta using the #await block syntax?

// SvxRenderer.svelte

<script lang="ts">
    import { loadSvx } from '$lib/loadSvx';
    import { fade } from 'svelte/transition';
    import Spinner from '$lib/components/ui/polish/spinner/Spinner.svelte';
    import ErrorRenderer from './ErrorRenderer.svelte';

    export let slug: string;
    export let frontMatter: any;

    let spinnerHidden = false;
</script>

{#await loadSvx(slug)}
    <div transition:fade on:outroend={() => (spinnerHidden = true)}>
        <Spinner></Spinner>
    </div>
{:then svx}
    {#if spinnerHidden}
        <div class="post-container" transition:fade>
            <svelte:component this={svx.content} />
        </div>
    {/if}
{:catch error}
    {#if spinnerHidden}
        <div transition:fade>
            <ErrorRenderer {error} />
        </div>
    {/if}
{/await}

I tried a silly workaround by adding {@const foo = () => (frontMatter = svx.meta )} to the #then block but that doesn't seem to work: if I then try to bind the frontMatter in the parent component, like so:

<script lang="ts">
    import SvxRenderer from '$lib/components/svx/SvxRenderer.svelte';
    import { page } from '$app/stores';

    let frontMatter: any = null;
</script>

{#if frontMatter !== null}
    <h1>{frontMatter.title}</h1>
{/if}
<SvxRenderer slug={$page.params.slug} bind:frontMatter />

The frontMatter stays null, and the title doesn't render.

(I realize I could do this by introducing an async function to onMount and using #if's to manually handle when my data is loaded instead, but I do feel like the #await block syntax is more self-explanatory, so I'm wondering if there's a way to do this with #await blocks.)


Solution

  • You could extend the promise chain in JS, something along the lines of:

    $: promise = loadSvx(slug)
        .then(result => {
            frontMatter = result.meta;
    
            return result;
        });
    
    {#await promise} ...
    

    (Written as reactive in case slug changes, though this still has a race condition that probably should be fixed.)