Skip to content

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

@sacrosanctic

Description

@sacrosanctic

Describe the bug

  • Reactive updates for local state changes are broken post-migration.
  • The migration tool does not address or mention this behavioural change.
  • $effect is discouraged, yet it is the only viable solution for handling updates that come from both the client and server. This is neither mentioned nor explained in the migration or core documentation.

Suggestions

  • The migration CLI should flag this change and recommend solutions.
  • Clarify when the use of $effect is appropriate

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 sveltejs/svelte#14536. 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 #13000.

Related discussions from maintainers

Related help threads

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions