svelte
svelte copied to clipboard
Bug: "Out" transition blocks component from unmounting indefinitely, caused by update to store
Describe the bug
Discovered this while making my own app which has a global array of objects as state. I noticed that my elements weren't unmounting due to {#if} statements as I was used to, and narrowed it down to it being due to transitions. It's a bit strange to explain, but I tried my best isolating the bug.
Reproduction
https://svelte.dev/repl/83c9ee0103da430aa199daadbee12318?version=3.55.1
Notes:
The bug goes away when you remove the out transition (in Comp) The bug does not happen if you inlined the Comp component into App.svelte
This is one of several transition bugs I've run into while using Svelte and I have to say, it really makes it hard to love or use. My project is full of transitions and animations, and this stuff not working is a huge deal-breaker and kinda makes me wanna use something else because I simply cannot realize my vision with Svelte. It would be great if the Svelte team could look into this and provide a fix.
System Info
macOS Ventura 13.1
Google Chrome Version 110.0.5481.177 (Official Build) (arm64)
Svelte 3.55.1
Severity
blocking all usage of svelte
Does appending |local
to your transition directive help as a workaround for now? e.g., transition:fade|local
No! Then there is no transition at all.
Right. I’m not sure if this is an issue with transitions. The if block in your component is never false, so the transition never takes effect. To get the fading effect, you can remove it (since the outer component already has an if block) or ensure the toggle changes the component state to true / false
Sure, the inner if
block is never false, yet the component gets unmounted. Clearly, according to my intuition, this should be playing any "out" transitions. At the very least, having the component Comp never disappear despite it clearly being excluded by the outer if
doesn't feel right.
An if
that is never false should be indistinguishable in behavior from not being there at all, but here this is not the case.
I have encountered this issue in production where a component is getting stuck on the screen when it receives a store update during its out transition. Here is a repl reproducing this bug: https://svelte.dev/repl/8e158d1f24fc4dd08f8bad9808ca6169?version=3.58.0
The requirements to reproduce are an out transition within an if block that depends on a store that is updated after the component is transitioning out due to an external condition.
@Vanilagy you have a small error in the REPL:
<script>
import { things } from './state.js';
import { fade } from 'svelte/transition';
$: thing = $things[0];
</script>
<div>
<!-- change this -->
<!-- {#if thing} -->
<!-- to this: -->
{#if !thing.done}
<p out:fade style="background: blue; color: white">
This should be fading out when toggling {thing.text}
</p>
{/if}
</div>
Your toggle function to update the things
store never sets the first item (thing) to a falsy value, so the if block and transition never trigger.
@jrmoynihan No, this is intentional. {#if thing}
should be equivalent to no if, since thing
never goes away. However, the component Comp
(which is properly conditionally rendered in App.svelte), fails to fade out.
I've managed to reproduce the bug using goto()
and a layout component (not actual layout), and it seems that it also affects capture()
I've made some progress on tracking down where the issue may be using the REPL @Vanilagy provided, but here's a video to show y'all that I'm not insane:
https://github.com/sveltejs/svelte/assets/7076802/dd6e8eab-37c6-4393-8ef9-3edd723dc7cd
Presumably it's got something to do with what transition_out()
does, but this issue has me wondering if it's something to do with the animation engine...
It's been a couple months since I looked at this, but IIRC, at least for the example above, it's related to how transitions are tied into the if
blocks and how the if
blocks respond to reactive updates. When an if
block toggles false, it propagates transition_out
down the tree. When it toggles true, transition_in
. This creates a race-condition where if there is a component with an out transition within a nested if block and that nested if
block toggles to true while the component is still outroing, it propagates transition_in
and cancels the outro and therefore keeps the component stuck on the screen illogically.
So what would cause that if
block to toggle to true? This isn't exclusive to if
blocks so I presume there are other race conditions that other blocks are also dealing with?
Could that be why the breakpoints cause the issue to resolve? How do we approach this? From the animation loop? Do we approach it via the style manager?
I can't explain why the breakpoints were changing the behaviour. Some weird low level browser implementation stuff I presume.
I think if
blocks need to know when their parents are outroing so they can propagate the transition_in
to their children only when their parent isn't outroing.
I cannot remember if this issue also affects other conditional blocks like each
, await
, etc.