Search code examples
sveltesvelte-5

Nested reactive state passed as a property


I have a SvelteKit app using Svelte 5. My app shows a basket, using a BasketLine component to show every product in the basket with its quantity:

<script lang="ts">
  interface Props {
    line: BasketLineWithOffers;
  }

  const { line }: Props = $props();
</script>

<div>
  <h2>{line.product.title}</h2>
  <NumberPicker min={1} bind:value={line.quantity} />
</div>

This is giving a runtime warning in the console:

[svelte] binding_property_non_reactive bind:value={line.quantity} (src/lib/basket/BasketLine.svelte:96:30) is binding to a non-reactive property https://svelte.dev/e/binding_property_non_reactive

Using the number picker (a simple component showing a number input as well as plus and minus button) doesn't change the quantity.

Using const { line = $bindable() }: Props = $props(); doesn't fix it either. So what is the correct way to pass a bindable line property to the BasketLine component in such a way that its properties are also bindable/reactive state? line is just a simple JSON object coming from the server.


Solution

  • In runes mode, the object passed in needs to be reactive, i.e. $state, otherwise modifications of the properties will not trigger updates. You will get warnings if you modify a property that is not marked $bindable but this has no runtime effect when modifying the property rather than reassigning it.

    To get rid of the warnings, the property should be declared $bindable and used with bind:line on the parent.

    E.g.

    <script>
        import Component from './Component.svelte';
    
        let line = $state({ quantity: 1 });
    </script>
    
    <Component bind:line />
    
    <!-- Component.svelte -->
    <script>
      const { line = $bindable() } = $props();
    </script>
    
    <input type=number min={1} bind:value={line.quantity} /> <br>
    
    Quantity: {line.quantity}
    

    Playground

    If you cannot control how the parent passes the object and you need local reactivity, you probably need to declare a local $state variable from the property (and possibly synchronize it on prop change via an $effect).