最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Svelte 5 props are undefined after unmount - Stack Overflow

programmeradmin2浏览0评论

I have a component that looks like this:

<script lang="ts">
  import Component2 from './Component2.svelte'

  const {value, setState} = $props()

  const closure = () => console.log('value is undefined:', value)

  const next = () => setState({component: Component2, props: {closure}})

  $inspect(value)
</script>

<button onclick={next}>Next</button>

The setState function changes state in the parent component, which then renders the passed component (Component2 in this case) with the given props. Component2 then calls the closure function.

The console output for this program (on a development build) is:

init 1
update undefined
value is undefined: undefined

What's happening is value is getting set to undefined when Component1 unmounts. value is closed over by the closure function, which means it is undefined when Component2 later calls it.

This change fixes the problem:

const valueCopy = value
const closure = () => console.log('value is not undefined:', copyValue)

This clarifies that the problem is not that value is a proxy with deep reactivity, but is being reassigned because it's a prop of Component.

The problem is that this behavior is new with svelte5, and didn't used to be the case in svelte4. This breaks a bunch of stuff with my (real) application. It also seems like very annoying behavior, since now I have to explicitly re-assign every variable I don't want to be destroyed, in every component where this (fairly complex) pattern exists.

Is there any other (better) way to handle this?

For completeness, my use case is controlling modal state. Here's an example from my real-world application:

const onEmoji = (emoji: NativeEmoji) => 
  publishReaction({event: e, relays: [url], content: emoji.unicode})

const showEmojiPicker = () =>
  pushModal(EmojiPicker, {onClick: onEmoji})

Minimal reproduction:

  • Source code:
  • Demo: /

I have a component that looks like this:

<script lang="ts">
  import Component2 from './Component2.svelte'

  const {value, setState} = $props()

  const closure = () => console.log('value is undefined:', value)

  const next = () => setState({component: Component2, props: {closure}})

  $inspect(value)
</script>

<button onclick={next}>Next</button>

The setState function changes state in the parent component, which then renders the passed component (Component2 in this case) with the given props. Component2 then calls the closure function.

The console output for this program (on a development build) is:

init 1
update undefined
value is undefined: undefined

What's happening is value is getting set to undefined when Component1 unmounts. value is closed over by the closure function, which means it is undefined when Component2 later calls it.

This change fixes the problem:

const valueCopy = value
const closure = () => console.log('value is not undefined:', copyValue)

This clarifies that the problem is not that value is a proxy with deep reactivity, but is being reassigned because it's a prop of Component.

The problem is that this behavior is new with svelte5, and didn't used to be the case in svelte4. This breaks a bunch of stuff with my (real) application. It also seems like very annoying behavior, since now I have to explicitly re-assign every variable I don't want to be destroyed, in every component where this (fairly complex) pattern exists.

Is there any other (better) way to handle this?

For completeness, my use case is controlling modal state. Here's an example from my real-world application:

const onEmoji = (emoji: NativeEmoji) => 
  publishReaction({event: e, relays: [url], content: emoji.unicode})

const showEmojiPicker = () =>
  pushModal(EmojiPicker, {onClick: onEmoji})

Minimal reproduction:

  • Source code: https://github/staab/svelte5-closure-bug
  • Demo: https://svelte5-closure-bug.onrender/
Share Improve this question edited Feb 17 at 18:23 brunnerh 185k30 gold badges357 silver badges430 bronze badges asked Feb 17 at 18:10 jstaabjstaab 3,8652 gold badges28 silver badges43 bronze badges 4
  • What's the purpose of exposing internal state this way? It is good practice for components to clean after themselves, which includes property values. Maybe you should attempt passing data around in a way that things don't depend on the lifecycle of components? It is difficult to say since I suppose this is a simplification of what you really need. – José Ramírez Commented Feb 17 at 22:30
  • My use case is managing modal dialog state. It also applies to anything similar to portals. My code does things like pushModal(MyModal, {callback}). MyModal is generic, and it's very idiomatic to pass functions as values in javascript. I don't like that svelte breaks this pattern. – jstaab Commented Feb 17 at 23:39
  • I see. So, Parent passes callback to ComponentX as a prop, then ComponentX triggers pushModal() in a fire-and-fet mode, unloading itself? Am I understanding? – José Ramírez Commented Feb 17 at 23:47
  • A calls PushModal(B, {callback}), B calls the callback. By that time, A is unmounted and any props of A that were closed over by callback have been set to undefined – jstaab Commented Feb 18 at 1:11
Add a comment  | 

1 Answer 1

Reset to default 0

I filed a bug report with the svelte team: https://github/sveltejs/svelte/issues/15325

The response was that this behavior is by design. Props "belong" to the parent component, and are passed as getters, which means they can be revoked at any time. I don't understand why this is necessary, but I'll take for granted it is.

发布评论

评论列表(0)

  1. 暂无评论