Home > Back-end >  Is it posible to use a State Machine without using Conditional Statements on the Current State?
Is it posible to use a State Machine without using Conditional Statements on the Current State?

Time:09-17

I want to use a state machine where the current state is mapped to the current screen. I am using svelte with xstate.
Example with string as type:
App.svelte

<script>
    import { interpret } from "xstate";
    import { toggleMachine } from "./machine";

    const toggleService = interpret(toggleMachine).start();
</script>

<button on:click={() => toggleService.send("TOGGLE")}>
    <p>{$toggleService.value}</p>
</button>

machine.js

import { createMachine } from 'xstate';

// This machine is completely decoupled from Svelte
export const toggleMachine = createMachine({
    id: 'toggle',
    initial: 'LOGIN',
    states: {
        LOBBY: {
            on: { TOGGLE: 'LOGIN' }
        },
        LOGIN: {
            on: { TOGGLE: 'LOBBY' }
        }
    }
});

How to do the same with a svelte component instead of a string?
Instead of <p>{$toggleService.value}</p> something like this <$toggleService.value />

Edit: What I tried:

  • Create Lobby and Login svelte component Login.svelte
<script lang="ts">
</script>

<main>
    <h1>Login</h1>
</main>

<style>
</style>

Lobby.svelte

<script lang="ts">
</script>

<main>
    <h1>Lobby</h1>
</main>

<style>
</style>

  • Use them in machine.js
import { createMachine } from 'xstate';
import Login from './Login.svelte'
import Lobby from './Lobby.svelte'
// This machine is completely decoupled from Svelte
export const toggleMachine = createMachine({
    id: 'toggle',
    initial: Login,
    states: {
        Login: {
            on: { TOGGLE: Lobby }
        },
        Lobby: {
            on: { TOGGLE: Login }
        }
    }
});
  • Use state machine in App.svelte App.svelte
<script>
    import { interpret } from "xstate";
    import { toggleMachine } from "./machine";

    const toggleService = interpret(toggleMachine).start();
</script>

<button on:click={() => toggleService.send("TOGGLE")}>
    <$toggleService.value />
</button>

I get the error:

[!] (plugin svelte) ParseError: Expected valid tag name
src/App.svelte
 7: 
 8: <button on:click={() => toggleService.send("TOGGLE")}>
 9:   <$toggleService.value />
       ^
10: </button>
ParseError: Expected valid tag name
    at error (/home/walde/projects/svelte/xstate/node_modules/svelte/src/compiler/utils/error.ts:25:16)
    at Parser$1.error (/home/walde/projects/svelte/xstate/node_modules/svelte/src/compiler/parse/index.ts:101:3)
    at read_tag_name (/home/walde/projects/svelte/xstate/node_modules/svelte/src/compiler/parse/state/tag.ts:267:10)
    at tag (/home/walde/projects/svelte/xstate/node_modules/svelte/src/compiler/parse/state/tag.ts:77:15)
    at new Parser$1 (/home/walde/projects/svelte/xstate/node_modules/svelte/src/compiler/parse/index.ts:53:12)
    at parse (/home/walde/projects/svelte/xstate/node_modules/svelte/src/compiler/parse/index.ts:218:17)
    at compile (/home/walde/projects/svelte/xstate/node_modules/svelte/src/compiler/compile/index.ts:93:14)
    at Object.transform (/home/walde/projects/svelte/xstate/node_modules/rollup-plugin-svelte/index.js:111:21)
    at /home/walde/projects/svelte/xstate/node_modules/rollup/dist/shared/rollup.js:20218:25

CodePudding user response:

I can't speak for the xstate-part, but the last snippet is definitely not working. You can't dynamically declare a tag like this. But you can use the special <svelte:component> element for this:

<script>
    import { interpret } from "xstate";
    import { toggleMachine } from "./machine";

    const toggleService = interpret(toggleMachine).start();
</script>

<button on:click={() => toggleService.send("TOGGLE")}>
    Toggle me
</button>
<svelte:component this={$toggleService.value} />

Docs: https://svelte.dev/docs#svelte_component

CodePudding user response:

Thanks to dummdidumm who helped with the solution for the svelte part.

The complete soloution for the problem how to use svelte and xstate together without writing a conditional statement is:

  • Bind the component to the context of the state machine
<svelte:component this={$toggleService.context.component} />
  • Change the state machine context on entry to a new state so it matches with the current state.
        Login: {
            on: { TOGGLE: "Lobby" },
            entry: assign({ component: (ctx) => ctx.component = Login }),
        }

complete working example:
App.svelte

<script>
    import { interpret } from "xstate";
    import { toggleMachine } from "./machine";
    const toggleService = interpret(toggleMachine).start();
</script>

<button on:click={() => toggleService.send("TOGGLE")}>
    {$toggleService.value}
</button>
<svelte:component this={$toggleService.context.component} />

Lobby.svelte

<script lang="ts">
</script>
<main>
    <h1>SVELTE Lobby</h1>
</main>
<style>
</style>

Login.svelte

<script lang="ts">
</script>
<main>
    <h1>SVELTE Login</h1>
</main>
<style>
</style>

machine.js

import { createMachine, assign } from 'xstate';
import Login from './Login.svelte'
import Lobby from './Lobby.svelte'
export const toggleMachine = createMachine({
    id: 'toggle',
    initial: "Login",
    context: {
        component: Lobby
    },
    states: {
        Login: {
            on: { TOGGLE: "Lobby" },
            entry: assign({ component: (ctx) => ctx.component = Login }),
        },
        Lobby: {
            on: { TOGGLE: "Login" },
            entry: assign({ component: (ctx) => ctx.component = Lobby }),
        }
    }
});
  • Related