svelte icon indicating copy to clipboard operation
svelte copied to clipboard

Bug: "Out" transition blocks component from unmounting indefinitely, caused by update to store

Open Vanilagy opened this issue 2 years ago • 14 comments

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

Vanilagy avatar Mar 04 '23 01:03 Vanilagy

Does appending |local to your transition directive help as a workaround for now? e.g., transition:fade|local

eltigerchino avatar Mar 04 '23 13:03 eltigerchino

No! Then there is no transition at all.

Vanilagy avatar Mar 06 '23 10:03 Vanilagy

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

eltigerchino avatar Mar 06 '23 12:03 eltigerchino

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.

Vanilagy avatar Mar 06 '23 18:03 Vanilagy

An if that is never false should be indistinguishable in behavior from not being there at all, but here this is not the case.

Vanilagy avatar Mar 06 '23 18:03 Vanilagy

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.

jrouleau avatar Apr 11 '23 23:04 jrouleau

@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 avatar Jun 05 '23 00:06 jrmoynihan

@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.

Vanilagy avatar Jun 05 '23 07:06 Vanilagy

I've managed to reproduce the bug using goto() and a layout component (not actual layout), and it seems that it also affects capture()

ezra-en avatar Jun 22 '23 08:06 ezra-en

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...

ezra-en avatar Jun 22 '23 15:06 ezra-en

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.

jrouleau avatar Jun 22 '23 16:06 jrouleau

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?

ezra-en avatar Jun 22 '23 17:06 ezra-en

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?

ezra-en avatar Jun 22 '23 17:06 ezra-en

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.

jrouleau avatar Jun 22 '23 19:06 jrouleau