-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
State is undefined in cleanup functions when component is recreated with {#key}
#15606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
It seems this isn’t limited to // App.svelte
<script>
import { onMount } from "svelte"
import Component from './Component.svelte';
let show = $state(true);
onMount(() => {
show = false;
})
</script>
{#if show}
<Component />
{/if} |
Dont use $state on intervalId, just: |
@Tichss Thanks! But this is just a simplified example for reproduction purposes. The issue is |
I usually keep the state entirely local if possible, i.e. $effect(() => {
const handle = setInterval(() => /* ... */, 1000)
return () => clearInterval(handle);
}); There is no way for this variable to change. |
https://svelte.dev/playground/08fd26bd386542d0a11873f616c09e1e?version=5.25.3 then add await tick() to destroy function. |
Thanks for the suggestions! I discovered this issue while debugging problems during Svelte 4 to 5 migration. While there are workarounds available, this appears to be an unexpected behavior change in Svelte 5. I believe it would be beneficial to document this difference. Reference (Svelte 4): https://svelte.dev/playground/3cbf4f16aff34da1ace6622094f027ab?version=4.2.19 |
It looks like this is working as intended. You're updating the key block in the mount phase, so it will use the previous value – which is |
My understanding was that the intended behavior is that you see the same value in teardown as you see in the body of |
@gyzerok That is how it is working, its providing the value it was when the effect was called – which is onMount(() => {
intervalId = setInterval(() => {}, 1000)
console.log(intervalId);
return () => {
console.log(intervalId);
}
});
onMount(() => {
console.log(intervalId);
return () => {
console.log(intervalId);
}
}); |
@trueadm personally I expect svelte here to behave like it inserts onMount(() => {
intervalId = setInterval(() => {}, 1000)
const intervalId = intervalId;
console.log(intervalId);
return () => {
console.log(intervalId);
}
});
onMount(() => {
const intervalId = intervalId;
console.log(intervalId);
return () => {
console.log(intervalId);
}
}); So that if we do |
@gyzerok That's not how this fix is implemented though, it's implemented on using the previous value that was read. Not the previous value that was written to, if I make the change to like how you say a bunch of tests now start failing. |
@trueadm was a bit challenging to find from which comment I've got the idea, but got it now (see in the answer to 3): #15469 (comment).
So either I misinterpret this sentence or it is closer to how I expect it to behave. And since you've said currently it's not working this way the question is if it should be adjusted to work this way or not? I'd advocate to adjust :) |
You're missing this statement in the same post:
The reason that is important is because both components are using onMount(() => {
key = 1;
})
intervalId = setInterval(() => {}, 1000) Then because key = 1 That invalidates the update, so both changes are now going to be rolled back to their previous value during teardown, including: intervalId = setInterval(() => {}, 1000) State changes that happen as part of the same sync work all fall into the same bucket. That's just how signal based systems work – they use microtasks to break up these cycles. If you break up the changes into different updates then things work as you'd expect: onMount(() => {
setTimeout(() => {
key = 1;
});
}) FWIW, this is a form of state tearing issue. We don't recommend making state changes in effects, as you can get tearing issues like this because of the fact that effects are meant to handle side-effects of external systems, not ones on the internal system. However you can easily work around it by doing |
I found out there was a subtle bug that actually was the cause for this in the end, so I'll put up a PR to address this issue. Thanks for bearing with me. |
Cool, thank you! |
Describe the bug
State becomes undefined in component cleanup functions (both
onMount
return functions andonDestroy
callbacks) when the component is recreated using a{#key}
block.Reproduction
https://svelte.dev/playground/6cece48d922a425d9be411b8711036aa?version=5.25.3
Logs
System Info
Severity
annoyance
The text was updated successfully, but these errors were encountered: