Search code examples
javascriptjsonrestsveltesveltekit

Svelte App Won't Render my Data in the Browser


I'm pretty new to Svelte and I'm trying to render my data object in the browser. I'm not sure what's going on as the backend seems to be okay and I'm able to print the data object in the console.

I have a backend flask api that reads the following url

https://api.le-systeme-solaire.net/rest/bodies/

That's up and working fine. If you look it up you can see what the json looks like

This is the top half of my index.svelte file:

<script>

let data = []

async function initFetchSolarSystemData() {
 let response = await fetch("http://127.0.0.1:5000/solar-system/gravity");
 data = await response.json();

console.log("data", data)
console.log(["id", data.bodies[0].id])
console.log(["gravity", data.bodies[0].gravity])
console.log(["bodies", data.bodies.length])
}

initFetchSolarSystemData()

</script>

So when it runs, I can see values in the console like:

['id', 'lune']
['gravity', 1.62]
'bodies', 287]

So that seems to be working fine as well. Here is the bottom portion of the file where I believe the error is stemming from:

<section>

    {#if data.length}
        {#each data.bodies as item}

            <div class="gravity">
              <h1 id="item">
                {item.gravity}
              </h1>
            </div>

        {/each}
    {/if}

</section>

When the I run the script, I get no errors in the console. Nothing renders. If I replace this line:

 {#if data.length}

with

{#if data.bodies.length}

I get:

Cannot read properties of undefined (reading 'length')

Which I'm confused about because these values are being read earlier above in the file.

I think I'm missing something here, and any help would be immensely appreciated.


Solution

  • tl;dr: There's an Array/Object mix-up; change the {#if data.length} to {#if data.bodies?.length}.

    Problem

    When the I run the script, I get no errors in the console. Nothing renders.

    When the {#if data.length} gets first evaluated, the data variable still holds the initial empty array, so the #if gets a falsy 0.

    This is also why replacing the {#if data.length} with {#if data.bodies.length} results in an error — data.bodies is undefined, so length lookup fails.

    Later, when the fetch completes, the data is assigned an Object, so the {#if data.length} gets this time a falsy undefined.

    As such, both before and after the fetch — nothing renders.


    Solutions

    The data should be an Object rather than an Array, and the #if should consequently check for the length of the data.bodies rather than the data itself.
    Then, either:

    #1: Await the fetch

    Wrap the <section>…</section>'s content with Svelte's await block as follows:

      {#await initFetchSolarSystemData()}
        <p>Loading…</p>
      {:then data}
        …
      {:catch error}
        <p>Uh oh, an error.</p>
      {/await}
    

    Also, to facilitate it:

    1. Modify the initFetchSolarSystemData() function to return the data.
    2. Remove the initFetchSolarSystemData() call from your <script>…</script> part.

    Demo: https://codesandbox.io/embed/lively-river-dni3l6?fontsize=14&hidenavigation=1&theme=dark


    #2: Get the false first and let Svelte trigger an update later

    (big thanks to @StephaneVanraes for pointing this out)

    Alternatively, the #if and #each could first silently "fail" when running yet before the fetch, and Svelte would subsequently trigger an update once the fetch finishes and the received object is assigned to data.

    This can be done, for example, either by setting up the data with a bodies property from the very beginning:

    let data = { bodies: [] }
    {#if data.bodies.length}
    

    or by using the optional chaining operator:

    {#if data.bodies?.length}
    

    Demo: https://codesandbox.io/embed/hungry-lederberg-78pr3g?fontsize=14&hidenavigation=1&theme=dark


    I hope this answers your question. =)
    Please also see the answer by @StephaneVanraes.