@xstate/svelte

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

Quick Start

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

Via CDN

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

By using the global variable XStateSvelte

or

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

By using the global variable XStateSvelteFSM

  1. Import useMachine
  1. <script>
  2. import { useMachine } from '@xstate/svelte';
  3. import { createMachine } from 'xstate';
  4. const toggleMachine = createMachine({
  5. id: 'toggle',
  6. initial: 'inactive',
  7. states: {
  8. inactive: {
  9. on: { TOGGLE: 'active' }
  10. },
  11. active: {
  12. on: { TOGGLE: 'inactive' }
  13. }
  14. }
  15. });
  16. const { state, send } = useMachine(toggleMachine);
  17. </script>
  18. <button on:click={() => send('TOGGLE')}>
  19. {$state.value === 'inactive'
  20. ? 'Click to activate'
  21. : 'Active! Click to deactivate'}
  22. </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

  1. <script>
  2. import { useMachine } from '@xstate/svelte/lib/fsm';
  3. import { createMachine, assign } from '@xstate/fsm';
  4. const fetchMachine = createMachine({
  5. id: 'fetch',
  6. initial: 'idle',
  7. context: {
  8. data: undefined
  9. },
  10. states: {
  11. idle: {
  12. on: { FETCH: 'loading' }
  13. },
  14. loading: {
  15. entry: ['load'],
  16. on: {
  17. RESOLVE: {
  18. target: 'success',
  19. actions: assign({
  20. data: (context, event) => event.data
  21. })
  22. }
  23. }
  24. },
  25. success: {}
  26. }
  27. });
  28. const onFetch = () => new Promise((res) => res('some data'));
  29. const { state, send } = useMachine(fetchMachine, {
  30. actions: {
  31. load: () => {
  32. onFetch().then((res) => {
  33. send({ type: 'RESOLVE', data: res });
  34. });
  35. }
  36. }
  37. });
  38. </script>
  39. {#if $state.value === 'idle'}
  40. <button on:click={() => send('FETCH')}>Fetch</button>
  41. {:else if $state.value === 'loading'}
  42. <div>Loading...</div>
  43. {:else if $state.value === 'success'}
  44. <div>
  45. Success! Data: <div data-testid="data">{$state.context.data}</div>
  46. </div>
  47. {/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

  1. <script lang="ts">
  2. import { interpret } from 'xstate';
  3. import { createModel } from 'xstate/lib/model';
  4. import { useSelector } from '../src';
  5. const model = createModel(
  6. {
  7. count: 0,
  8. anotherCount: 0
  9. },
  10. {
  11. events: {
  12. INCREMENT: () => ({}),
  13. INCREMENT_ANOTHER: () => ({})
  14. }
  15. }
  16. );
  17. const machine = model.createMachine({
  18. initial: 'idle',
  19. context: model.initialContext,
  20. states: {
  21. idle: {
  22. on: {
  23. INCREMENT: {
  24. actions: model.assign({ count: ({ count }) => count + 1 })
  25. },
  26. INCREMENT_ANOTHER: {
  27. actions: model.assign({
  28. anotherCount: ({ anotherCount }) => anotherCount + 1
  29. })
  30. }
  31. }
  32. }
  33. }
  34. });
  35. const service = interpret(machine).start();
  36. const count = useSelector(service, (state) => state.context.count);
  37. let withSelector = 0;
  38. $: $count && withSelector++;
  39. let withoutSelector = 0;
  40. $: $service.context.count && withoutSelector++;
  41. </script>
  42. <button data-testid="count" on:click={() => service.send('INCREMENT')}
  43. >Increment count</button
  44. >
  45. <button data-testid="another" on:click={() => service.send('INCREMENT_ANOTHER')}
  46. >Increment another count</button
  47. >
  48. <div data-testid="withSelector">{withSelector}</div>
  49. <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:

  1. <script>
  2. import { useMachine } from '@xstate/svelte';
  3. import { createMachine, assign } from 'xstate';
  4. const fetchMachine = createMachine({
  5. id: 'fetch',
  6. initial: 'idle',
  7. context: {
  8. data: undefined,
  9. error: undefined
  10. },
  11. states: {
  12. idle: {
  13. on: { FETCH: 'loading' }
  14. },
  15. loading: {
  16. invoke: {
  17. src: 'fetchData',
  18. onDone: {
  19. target: 'success',
  20. actions: assign({
  21. data: (_, event) => event.data
  22. })
  23. },
  24. onError: {
  25. target: 'failure',
  26. actions: assign({
  27. error: (_, event) => event.data
  28. })
  29. }
  30. }
  31. },
  32. success: {
  33. entry: 'notifySuccess',
  34. type: 'final'
  35. },
  36. failure: {
  37. on: {
  38. RETRY: 'loading'
  39. }
  40. }
  41. }
  42. });
  43. const onResolve = (data) => {
  44. // Do something with data
  45. };
  46. const { state, send } = useMachine(fetchMachine, {
  47. actions: {
  48. notifySuccess: (context) => onResolve(context.data)
  49. },
  50. services: {
  51. fetchData: (_, event) =>
  52. fetch(`some/api/${event.query}`).then((res) => res.json())
  53. }
  54. });
  55. </script>
  56. {#if $state.value === 'idle'}
  57. <button on:click={() => send('FETCH', { query: 'something' })}>
  58. Search for something
  59. </button>
  60. {:else if $state.value === 'loading'}
  61. <div>Searching...</div>
  62. {:else if $state.value === 'success'}
  63. <div>Success! Data: {$state.context.data}</div>
  64. {:else if $state.value === 'failure'}
  65. <p>{$state.context.error.message}</p>
  66. <button on:click={() => send('RETRY')}>Retry</button>
  67. {/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(...).

  1. {#if $state.matches('idle')}
  2. //
  3. {:else if $state.matches({ loading: 'user' })}
  4. //
  5. {:else if $state.matches({ loading: 'friends' })}
  6. //
  7. {/if}

Persisted and Rehydrated State

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

  1. // Get the persisted state config object from somewhere, e.g. localStorage
  2. const persistedState = JSON.parse(
  3. localStorage.getItem('some-persisted-state-key')
  4. );
  5. const { state, send } = useMachine(someMachine, {
  6. state: persistedState
  7. });
  8. // 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

  1. // service.js
  2. import { createMachine, interpret } from 'xstate';
  3. const toggleMachine = createMachine({
  4. id: 'toggle',
  5. initial: 'inactive',
  6. states: {
  7. inactive: {
  8. on: { TOGGLE: 'active' }
  9. },
  10. active: {
  11. on: { TOGGLE: 'inactive' }
  12. }
  13. }
  14. });
  15. export const toggleService = interpret(toggleMachine).start();
  1. // App.svelte
  2. <script>
  3. import { toggleService } from './service';
  4. </script>
  5. <button on:click={() => toggleService.send('TOGGLE')}>
  6. {$toggleService.value === 'inactive'
  7. ? 'Click to activate'
  8. : 'Active! Click to deactivate'}
  9. </button>