sveltesvelte-5

Svelte 5 - $derived.by not working as expected


I have following function:

function createProducts(){

    /**
     * @type {Array.<{
     * id: Number,
     * name: String,
     * product: Array.<{
     * id: Number,
     * name: String,
     * price: Number,
     * kitchen: Boolean,
     * productCategoryId: Number
     * }>
     * }>}
     */
    let productCategories = $state([])

    let allProducts = $derived.by(() => {

        let allProducts = []

        Object.entries(productCategories).forEach(entry => {
            const [key, value] = entry;
            console.log(key, value);
            allProducts.push(entry.products)
          });

        return allProducts
    })


    return {
        get productCategories(){
            return productCategories
        },
        set productCategories(newValue){
            productCategories = newValue
        },
        allProducts
    }

}

export const products = createProducts()

And this is where the productCategories are populated:

<script>
    import { authorization } from "../store/authorization.svelte";
    import Tables from "./Tables.svelte";
    import { tables } from "../store/tables.svelte";
    import Dialog from "./Dialog.svelte";
    import { products } from "../store/products.svelte";

    const _token = localStorage.getItem('_token')

    $effect(() => {
      fetch('http://localhost:9555/api/home', {
          method: 'get',
          headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json; charset=UTF-8',
        Authorization: `Bearer ${_token}`
    }
        }
          )
          .then(resp => resp.json())
          .then(json => {

            products.productCategories.push(json.productCategories)
      })
    })

    function logout(){
        fetch('http://localhost:9555/api/logout', {
          method: 'POST',
          headers: {
              'Accept': 'application/json',
              'Content-Type': 'application/json; charset=UTF-8',
              Authorization: `Bearer ${_token}`
          }
        }
          )
          .then(resp => resp.json())
          .then(json => {
            authorization.logout()
        })
    }
</script>

When I try to make the list of the products, the products.allProducts is empty - for some reason the $derived.by is not working, and I can't figure it out why it is not working. What am I doing wrong hjere?

<div id="sidebar">
    {#each products.allProducts as product}
        {product.name}</span>
    {/each}
</div>

Solution

  • Like $state, derived values have to be closed over, otherwise you just evaluate their current state once and any updates will not be captured:

    const allProducts = $dervide.by(...);
    
    return {
      // ...
      get allProducts() { return allProducts; },
    };
    

    Simple standalone example:

    <script>
        function createCounter() {
            let value = $state(1);
            const double = $derived(value * 2);
    
            return {
                get value() { return value; },
                set value(v) { value = v; },
                get double() { return double; },
            };
        }
    
        const counter = createCounter();
    </script>
    
    <input bind:value={counter.value} type="number"/>
    Double: {counter.double}
    

    In many cases $derived/$derived.by only provides value caching and is not strictly necessary for reactivity, so if you do not need caching you can also skip it:

    function createCounter() {
        const counter = $state({
            value: 1,
            get double() { return this.value * 2; }
        });
    
        return counter;
    }