Search code examples
vitesveltesveltekit

Sveltekit + Vite - Store not reacting properly when the file using it isn't modified


I don't really know what the problem is, but here's the situation:

I have a custom store containing an array getting its data from an API. In my main +page.svelte file, I initialise this store in onMount() and display its contents.

This works exactly as intended - my data appears on the page -, BUT, as soon as I reload the page or compile/save, it acts as if my store is empty (no error message, but no data either). Logging the content of my store shows an empty array (Array []), however it is filled with content: Array empty but filled

However, as soon as I modify this +page.svelte (even just adding a comment, inside or outside of the script tag), my data appears. When logged, it does correctly show a filled array (Array(3) [ {...}, {...}, {...} ]): Array filled
Edit: modifying its child or the +layout.svelte component doesn't break the store, however when broken it doesn't fix it.

Basically, whatever I am doing, if I don't modify this main page, it's not behaving correctly.

My project has been created using the sveltekit bootstrap npm create svelte@latest choosing typescript, eslint and prettier, and I've added SCSS. I keep seeing "vite" in console, so I guess it's in there too?

I'm in dev mode (I ran npm run dev), and everything works without any compiling problems.

I've tried looking at some config files, but honestly I don't understand anything.

The same way, I've tried looking up on the internet, but since I can't really define the problem it's kinda hard.

Someone IRL (working as a C dev, so they can't help) suggests it is a compiler issue, but is it really? And if so, can I do anything about it?

I have to mention I don't really know anything technical outside of basic scripting, so please ELI5.

+page.svelte

<script lang="ts">
import { onMount } from "svelte";
import { meals } from "../store/meals";
import MealCard from "./MealCard.svelte";

onMount( () => {
    meals.initialise();
});
</script>

<h1>Today's Selection</h1>
<div class="list">
    {#each $meals as meal}
        <MealCard meal={meal}/>
    {/each}
</div>

meal.ts (the store) - typing remove for clarity

import { writable } from "svelte/store";

function createMeals() {
    const meals = [{
        id: "",
        name: "",
        preview: "",
        ingredients: [],
        tags: [],
        category: "",
        origin: "",
        source: "",
        video: "",
        instructions: "",
        locked: false
    }];

    const nbOfMeals = 3;
    
    const {subscribe} = writable(meals);
    
    return {
        subscribe,
        add: (meal) => {
            meals.push(meal);
        },

        initialise: async () => {
            if (meals.length === nbOfMeals) {
                return;
            }
            for (let iMeal = 0; iMeal < nbOfMeals; iMeal++) {
                getMeal();
            }
            meals.shift();
        }
    }
}

export const meals = createMeals();

async function getMeal() {
    const meal = {
        id: "1",
        name: "",
        preview: "",
        ingredients: [""],
        tags: [],
        category: "",
        origin: "",
        source: "",
        video: "",
        instructions: "",
        locked: false,
    };
    const url = "https://www.themealdb.com/api/json/v1/1/random.php";

    await fetch(url)
    .then((response) => response.json())
    .then((data) => {
        const _data = data.meals[0];

        meal.id = _data.idMeal;
        meal.name = _data.strMeal;
        meal.preview = _data.strMealThumb;
        
        const tags = _data.strTags;
        if (tags) {
            meal.tags = tags.split(",");
        }
        
        meal.category = _data.strCategory;
        meal.origin = _data.strArea;
        meal.source = _data.strSource;
        meal.video = _data.strYoutube;
        meal.instructions = _data.strInstructions;

        meal.ingredients = [""];
        
        let iIngr = 1;
        while (_data["strIngredient" + iIngr]) {
            let toAdd;
            const regexp = /\d|¼|½|¾|(\w+ of)/;

            if (regexp.test(_data["strMeasure" + iIngr])) {
            toAdd =
            _data["strMeasure" + iIngr] +
                " " +
                _data["strIngredient" + iIngr];
            } 

            else if (/[^\w]/.test(_data["strMeasure" + iIngr])) {
            toAdd = _data["strIngredient" + iIngr];
            } 

            else {
            toAdd =
            _data["strIngredient" + iIngr] +
                " (" +
                _data["strMeasure" + iIngr].toLowerCase() +
                ")";
            }

            meal.ingredients.push(toAdd);
            iIngr++;
        }
        meal.ingredients.shift();
        meals.add(meal);
    });
}

Solution

  • [...]as soon as I reload the page or compile/save, it acts as if my store is empty[...]

    This is probably due to server-side rendering somehow. When your web browser shows the page for the first time/reloads the page, your Svelte application runs on the server, and the server sends back the generated HTML code for it. Thereafter the web browser will start to run your Svelte application itself (if the client-side JS is enabled in the web browser).

    The server will send back the HTML code for the page before your store has been fully loaded. In onMount(), you call meals.initialize(), but on the server, onMount() will never be called. And even if it would be, you would need to use await on getMeal() in initialize() (but then you would probably also want to use Promise.all(), so you run all the promises at the same time).

    Can you confirm that this is the problem you are experiencing? Don't want to waste time on suggesting different solutions if this is not the problem ^^

    And in getMeal() you should not use then()/ catch() if you use await, but instead use try and catch.