Skip to content
Version: XState v5

@xstate/svelte

The @xstate/svelte package contains utilities for using XState with Svelte.

Quick start​

  1. Install xstate and @xstate/svelte:
npm i xstate @xstate/svelte

Via CDN

<script src="https://unpkg.com/@xstate/svelte/dist/xstate-svelte.min.js"></script>

By using the global variable XStateSvelte

or

<script src="https://unpkg.com/@xstate/svelte/dist/xstate-svelte.fsm.min.js"></script>

By using the global variable XStateSvelteFSM

  1. Import useMachine
<script>
import { useMachine } from '@xstate/svelte';
import { createMachine } from 'xstate';

const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: {
on: { TOGGLE: 'active' }
},
active: {
on: { TOGGLE: 'inactive' }
}
}
});

const { state, send } = useMachine(toggleMachine);
</script>

<button on:click={() => send('TOGGLE')}>
{$state.value === 'inactive'
? 'Click to activate'
: 'Active! Click to deactivate'}
</button>

API​

useMachine(machine, options?)​

A function that interprets the given machine and starts a service that runs for the lifetime of the component.

Arguments

  • machine - An XState machine.
  • options (optional) - Interpreter options OR one of the following Machine Config options: guards, actions, activities, services, delays, immediate, context, or state.

Returns { state, send, service}:

  • state - A Svelte store representing the current state of the machine as an XState State object. You should reference the store value by prefixing with $ i.e. $state.
  • send - A function that sends events to the running service.
  • service - The created service.

useMachine(machine) with @xstate/fsm​

A function that interprets the given finite state machine from [@xstate/fsm] and starts a service that runs for the lifetime of the component.

This special useMachine hook is imported from @xstate/svelte/lib/fsm

Arguments

Returns an object {state, send, service}:

  • state - A Svelte store representing the current state of the machine as an @xstate/fsm StateMachine.State object. You should reference the store value by prefixing with $ i.e. $state.
  • send - A function that sends events to the running service.
  • service - The created @xstate/fsm service.

Example

<script>
import { useMachine } from '@xstate/svelte/lib/fsm';
import { createMachine, assign } from '@xstate/fsm';

const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: {
data: undefined
},
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
entry: ['load'],
on: {
RESOLVE: {
target: 'success',
actions: assign({
data: (context, event) => event.data
})
}
}
},
success: {}
}
});

const onFetch = () => new Promise((res) => res('some data'));

const { state, send } = useMachine(fetchMachine, {
actions: {
load: () => {
onFetch().then((res) => {
send({ type: 'RESOLVE', data: res });
});
}
}
});
</script>

{#if $state.value === 'idle'}
<button on:click={() => send('FETCH')}>Fetch</button>
{:else if $state.value === 'loading'}
<div>Loading...</div>
{:else if $state.value === 'success'}
<div>
Success! Data: <div data-testid="data">{$state.context.data}</div>
</div>
{/if}

useSelector(actor, selector, compare?, getSnapshot?)​

A function that returns Svelte store representing the selected value from the snapshot of an actor, such as a service. The store will only be updated when the selected value changes, as determined by the optional compare function.

Arguments

  • actor - a service or an actor-like object that contains .send(...) and .subscribe(...) methods.
  • selector - a function that takes in an actor’s "current state" (snapshot) as an argument and returns the desired selected value.
  • compare (optional) - a function that determines if the current selected value is the same as the previous selected value.

Example

<script lang="ts">
import { interpret } from 'xstate';
import { createModel } from 'xstate/lib/model';
import { useSelector } from '../src';

const model = createModel(
{
count: 0,
anotherCount: 0
},
{
events: {
INCREMENT: () => ({}),
INCREMENT_ANOTHER: () => ({})
}
}
);

const machine = model.createMachine({
initial: 'idle',
context: model.initialContext,
states: {
idle: {
on: {
INCREMENT: {
actions: model.assign({ count: ({ count }) => count + 1 })
},
INCREMENT_ANOTHER: {
actions: model.assign({
anotherCount: ({ anotherCount }) => anotherCount + 1
})
}
}
}
}
});

const service = interpret(machine).start();

const count = useSelector(service, (state) => state.context.count);

let withSelector = 0;
$: $count && withSelector++;
let withoutSelector = 0;
$: $service.context.count && withoutSelector++;
</script>

<button data-testid="count" on:click={() => service.send('INCREMENT')}
>Increment count</button
>
<button data-testid="another" on:click={() => service.send('INCREMENT_ANOTHER')}
>Increment another count</button
>

<div data-testid="withSelector">{withSelector}</div>
<div data-testid="withoutSelector">{withoutSelector}</div>

Configuring Machines​

Existing machines can be configured by passing the machine options as the 2nd argument of useMachine(machine, options).

Example: the 'fetchData' service and 'notifySuccess' action are both configurable:

<script>
import { useMachine } from '@xstate/svelte';
import { createMachine, assign } from 'xstate';

const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: {
data: undefined,
error: undefined
},
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
invoke: {
src: 'fetchData',
onDone: {
target: 'success',
actions: assign({
data: (_, event) => event.data
})
},
onError: {
target: 'failure',
actions: assign({
error: (_, event) => event.data
})
}
}
},
success: {
entry: 'notifySuccess',
type: 'final'
},
failure: {
on: {
RETRY: 'loading'
}
}
}
});

const onResolve = (data) => {
// Do something with data
};

const { state, send } = useMachine(fetchMachine, {
actions: {
notifySuccess: (context) => onResolve(context.data)
},
services: {
fetchData: (_, event) =>
fetch(`some/api/${event.query}`).then((res) => res.json())
}
});
</script>

{#if $state.value === 'idle'}
<button on:click={() => send({ type: 'FETCH', query: 'something' })}>
Search for something
</button>
{:else if $state.value === 'loading'}
<div>Searching...</div>
{:else if $state.value === 'success'}
<div>Success! Data: {$state.context.data}</div>
{:else if $state.value === 'failure'}
<p>{$state.context.error.message}</p>
<button on:click={() => send('RETRY')}>Retry</button>
{/if}

Matching States​

When using hierarchical and parallel machines, the state values will be objects, not strings. In this case, it is best to use state.matches(...).

{#if $state.matches('idle')}
//
{:else if $state.matches({ loading: 'user' })}
//
{:else if $state.matches({ loading: 'friends' })}
//
{/if}

Persisted and Rehydrated State​

You can persist and rehydrate state with useMachine(...) via options.state:

// Get the persisted state config object from somewhere, e.g. localStorage

const persistedState = JSON.parse(
localStorage.getItem('some-persisted-state-key')
);

const { state, send } = useMachine(someMachine, {
state: persistedState
});

// state will initially be that persisted state, not the machine’s initialState

Services​

XState services implement the Svelte store contract. Existing services and spawned actors can therefore be accessed directly and subscriptions are handled automatically by prefixing the service name with $.

Example:

// service.js

import { createMachine, interpret } from 'xstate';

const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: {
on: { TOGGLE: 'active' }
},
active: {
on: { TOGGLE: 'inactive' }
}
}
});

export const toggleService = interpret(toggleMachine).start();
// App.svelte

<script>
import { toggleService } from './service';
</script>

<button on:click={() => toggleService.send('TOGGLE')}>
{$toggleService.value === 'inactive'
? 'Click to activate'
: 'Active! Click to deactivate'}
</button>