sveltejs / kit

web development, streamlined
https://svelte.dev/docs/kit
MIT License
18.73k stars 1.94k forks source link

The `data` prop in `+page.svelte` and `+layout.svelte` is no longer reactive after migrating to svelte 5 #12999

Open sacrosanctic opened 1 day ago

sacrosanctic commented 1 day ago

Describe the bug

Suggestions

Step 1 - Svelte 4 Reactivity

<script>
    export let data
</script>

{data.value}

<button on:click={() => { data.value++ }}>add one</button>

repl: https://www.sveltelab.dev/zbbk7q85vpwanc2

This is a typical reactive setup with local state update for optimistic UI.

Step 2 - Migrate to svelte 5 via npx sv migrate svelte-5

<script>
    let { data = $bindable() } = $props()
</script>

{data.value}

<button onclick={() => { data.value++ }}>add one</button>

repl: https://www.sveltelab.dev/98hgsqyo40rxg8f

The CLI replaced export let with $props, but we're no longer able to get reactive updates for data.value. $bindable() was also added for no observable effect.

Step 3 - Use $state

<script>
    let { data = $bindable() } = $props()
    let value = $state(data.value)
</script>

{value}

<button onclick={() => { value++ }}>add one</button>

repl: https://www.sveltelab.dev/4mxyw4ufpb7xd46

Local updates now work, but we're no longer able to receive updates from the server.

Step 3.1 - Use $derived

<script>
    let { data = $bindable() } = $props()
    let value = $derived(data.value)
</script>

{value}

<!-- <button onclick={() => { value++ }}>add one</button> -->

repl: https://www.sveltelab.dev/ccquv598riknpuu

Cannot assign to derived state. :|

Step 3.2 - Use $state + $effect

<script>
    let { data = $bindable() } = $props()
    let value = $state(data.value)
    $effect(() => {
        value = data.value;
    });
</script>

{value}

<button onclick={() => { value++ }}>add one</button>

repl: https://www.sveltelab.dev/pj3m9zhjahswqk2

Finally, we're able to return to the svelte 4 behaviour. This is also the solution @benmccann suggests in https://github.com/sveltejs/kit/issues/12902#issuecomment-2457804575. But this solution is a problem, as he puts it.

why are we encouraging this? it seems so gross and feels like anti-pattern source

On top of that step 3 and 3.1 were intuitive solutions a user will reach for, yet it is incorrect.

Severity

blocking an upgrade

Additional Information

Global State

I think there is also a high correlation between this and setting up a global state, as users often will and are encouraged to set up their data in the load fn and would like to use them in various pages. I created a second issue that addresses that in https://github.com/sveltejs/kit/issues/13000.

Related discussions from maintainers

Related help threads

PatrickG commented 6 hours ago

I guess this could be fixed when Sveltekit would use $state() for the data prop. Until then, I think I found a good solution for this.

// +page.svelte
let props = $props();

let data = $derived.by(() => {
  let state = $state(props.data);
  return state;
});
henrykrinkle01 commented 4 hours ago

While this works, it's still not intuitive, not documented and migration still breaks apps