Skip to content

Svelte 5: Props consumed from onDestroy are revalidated if not consumed in the template #14025

@wildyaboku

Description

@wildyaboku

Describe the bug

This is hard for me to explain, but I will try my best. I first identified this issue from my SvelteKit app after migrating, but was able to replicate the bad behavior in standalone Svelte and even on the REPL.

When passing a prop to a child component, the value of the prop is seemingly recomputed or revalidated during dismount if both of the following are true:

  • The value of the prop is consumed by the component in some way during onDestroy
  • The value of the prop is NOT consumed by the component's template

To demonstrate this, I wrapped the value of the prop (from the parent component) in a middleman logger function, e.g.

<DataConsumer data={log(data)} />

Ideally, this log function would only run initially when the component mounts (and in other situations, when the data changes).

However, when the child component is dismounted, that log middleman function is invoked by Svelte an extra time when the above conditions are true. Otherwise, the log function is invoked as expected.

To clarify, a middleman function is not required, and is merely being used to show that the prop is being revalidated. Any complex logic in the prop can cause render-breaking errors.

In an environment like SvelteKit, the data is often undefined at this time (such as when it is derived from page data and we're navigating away), which often led to "Cannot read properties of undefined" errors. For example:

<LinkedData data={data.reviews.richData} />

Currently in SvelteKit, if the LinkedData component matches all of the above conditions, then this code would cause a render-breaking error when navigating away, as data becomes undefined before it attempts to revalidate the prop expression data.reviews.richData.

Reproduction

https://svelte.dev/playground/085d926d44e845e6b3d06cc59a6f9fc8?version=5.1.4

The "toggle consumer" button will mount/dismount the child component. Toggling it with initial state will yield correct behavior, in that the log function is only called once at mounting time:

Data computed for prop: { example: "This is some example data" }
DataConsumer mounted
DataConsumer destroyed

However, when only the "Reference data in onDestroy?" checkbox is enabled, the log function is invoked an extra during unmounting, which is clear from the duplicate log line.

Data computed for prop: { example: "This is some example data" }
DataConsumer mounted
Data computed for prop: { example: "This is some example data" }
DataConsumer destroyed

The extra invocation of the log function directly corresponds to where in the onDestroy function the property's value was used. For example, you can move it below the console.log and the above output would be rearranged.

If both checkboxes are enabled (i.e. the template somehow consumes the prop) then the behavior returns to normal.

Logs

No response

System Info

Google Chrome 130.0.6723.70
Svelte 5.1.4

Severity

blocking an upgrade

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions