statelyai / xstate

Actor-based state management & orchestration for complex app logic.
https://stately.ai/docs
MIT License
26.51k stars 1.22k forks source link

Bug: Svelte's snapshot store out of sync #4941

Open phcoliveira opened 3 weeks ago

phcoliveira commented 3 weeks ago

XState version

XState version 5

Description

I am finishing to upgrade XState to v5 in a project using SvelteKit 4. However, I am finding some problems that I didn't have when using XState v4.

Previously, I had many occurrences like this, and they used to work fine.

<script>
import {useMachine} from '@xstate/svelte';
import {goto} from '$app/navigation';

import {fooMachine} from './foo.ts';

const {state} = useMachine(fooMachine);
</script>

{#if $state.matches('active')}
  <slot />
{:else}
  {#await goto('/somewhere/else')}
    Loading
  {/await}
{/if}

Now, with v5, the $snapshot store does not appear to work properly, or at least not fast enough because I get endless loops of redirection.

For fixing them, I had to do this.

<script>
import {useActor, useSelector} from '@xstate/svelte';
import {goto} from '$app/navigation';

import {fooMachine} from './foo.ts';

const {actorRef} = useActor(fooMachine);
const isActive = useSelector(actorRef, (snapshot) => {
  return snapshot.matches('active');
});
</script>

{#if $isActive}
  <slot />
{:else}
  {#await goto('/somewhere/else')}
    Loading
  {/await}
{/if}

Expected result

The $sessionSnapshot store, returned by useActor(), should match the state active.

Actual result

The $sessionSnapshot store remains in its initial state: inactive. However, a new subscription using useSelector() is updated.

Reproduction

https://codesandbox.io/p/github/phcoliveira/xstate-snapshot-problem/main?import=true

Additional context

The main files to checkout are:

src/lib/require-activated/require-activated.svelte
src/routes/activating/+page.svelte

But please do look around.

There are ways to circumvent this. For example, having the promise inside the logic of the machine and sending an event usually works too.

But I included in the first file a strange fix that uses useSelector instead of using the snapshot returned by useActor.

I can not really confirm that it used to work that way with XState 4 because, back then, I had these many actors (services, back then) inside one machine. When upgrading to XState 5, I dismembered them in order to make smaller machines and reduce the initial bundle size.