sveltesveltekitsvelte-store

state not updating in SvelteKit


I am currently building this e-commerce store and I want to have a modal as my cart using skeleton UI. Everything works except that the cart doesn't update immediately, but if I close the modal and open it again everything works fine. I also use the local storage store by skeleton UI. Here is my cart.svelte:

<script lang="ts">
    import CartProduct from './cartProduct.svelte';
    import { cartItems } from './cart';
    import { get, writable, type Writable } from 'svelte/store';
    import { getProductData } from '../routes/products';

    let subtotal = writable(0);
    const getSubtotal = () => {
        let total = 0;
        $cartItems.forEach((item) => {
            if (item && item.id) {
                const productData = getProductData(item.id);
                if (productData) {
                    total += productData.price * item.quantity;
                }
            }
        });
        subtotal.set(total);
    };

    $: getSubtotal();
</script>

<div class="bg-surface-100-800-token card p-8 w-[90%] max-h-[70dvh] relative">
    <h1 class="h1 font-black mb-4">Your Cart:</h1>
    <div class="overflow-auto max-h-[40dvh]">
        {#each $cartItems as item}
            <CartProduct itemId={item.id} />
        {/each}
    </div>
    <p>Subtotal: {$subtotal}</p>
    <button
        class="btn variant-ghost-primary mt-4 h3 font-bold"
        on:click={() => console.log('Checkout')}>Check out</button
    >
</div>

Here is my cart.ts:

import { get, type Writable } from 'svelte/store';
import { localStorageStore } from '@skeletonlabs/skeleton';

export const cartItems: Writable<CartItem[]> = localStorageStore<CartItem[]>('cartItems', []);

export const addToCart = (id: string) => {
    let items = get(cartItems);
    let itemPosition = items.findIndex((item) => {
        return item.id === id;
    });

    if (itemPosition === -1) {
        cartItems.update(() => {
            return [...items, { id: id, quantity: 1 }];
        });
    } else {
        return items;
    }
};

export const removeFromCart = (id: string) => {
    let items = get(cartItems);
    let itemPosition = items.findIndex((item) => item.id === id);

    if (itemPosition !== -1) {
        cartItems.update((cartItems) => {
            return cartItems.filter((item) => item.id !== id);
        });
    }
};

The state of the cart should be updated in realtime in my modal


Solution

  • The reactive statement

        $: getSubtotal();
    

    by itself has very limited use because it has no right-hand side variable to be reactive upon. This causes the statement to run only once - when the component (i.e. your modal) is instantied.

    Try modifying the signature of your function to

        const getSubtotal = (items) => {
            let total = 0;
            items.forEach((item) => {
                if (item && item.id) {
                    const productData = getProductData(item.id);
                    if (productData) {
                        total += productData.price * item.quantity;
                    }
                }
            });
            subtotal.set(total);
        };
    

    and your reactive statement to

        $: getSubtotal($cartItems);
    

    which in effect is saying "run this function again everytime $cartItems changes", and then uses the updated $cartItems value as a parameter in your computation function.