# @xstate/vue

The @xstate/vue package (opens new window) contains utilities for using XState (opens new window) with Vue (opens new window).

Vue 2 Notice:

If you're using Vue 2.x, please see the Vue recipe instead, or use the xstate-vue2 package (opens new window) if you want to use the Vue Composition API.

# Quick Start

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

Via CDN

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

By using the global variable XStateVue

  1. Import the useMachine composition function:
<script setup>
import { useMachine } from '@xstate/vue';
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>

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

# API

# useMachine(machine, options?)

A Vue composition function (opens new window) that interprets the given machine and starts a service that runs for the lifetime of the component.

Arguments

Returns { state, send, service}:

  • state - Represents the current state of the machine as an XState State object.
  • send - A function that sends events to the running service.
  • service - The created service.

# useActor(actor, getSnapshot)

A Vue composition function (opens new window) that subscribes to emitted changes from an existing actor (opens new window).

Since 0.5.0

Arguments

  • actor - an actor-like object that contains .send(...) and .subscribe(...) methods.
  • getSnapshot - a function that should return the latest emitted value from the actor.
    • Defaults to attempting to get the snapshot from actor.getSnapshot(), or returning undefined if that does not exist.
import { useActor } from '@xstate/vue';

const props = defineProps(['someSpawnedActor']);

const { state, send } = useActor(props.someSpawnedActor);

To subscribe to changes on the an actor whilst retaining reactivity from props or another reactive variable, Vue's computed (opens new window) can be used.

const { state, send } = useActor(computed(() => props.someSpawnedActor));

# useInterpret(machine, options?, observer?)

A Vue composition function (opens new window) that returns the service created from the machine with the options, if specified. It also sets up a subscription to the service with the observer, if provided.

Since 0.5.0

Arguments

  • machine - An XState machine (opens new window) or a function that lazily returns a machine.
  • options (optional) - Interpreter options (opens new window) and/or any of the following machine config options: guards, actions, services, delays, immediate, context, state.
  • observer (optional) - an observer or listener that listens to state updates:
    • an observer (e.g., { next: (state) => {/* ... */} })
    • or a listener (e.g., (state) => {/* ... */})
import { useInterpret } from '@xstate/vue';
import { someMachine } from '../path/to/someMachine';

const service = useInterpret(someMachine);

With options + listener:

import { useInterpret } from '@xstate/vue';
import { someMachine } from '../path/to/someMachine';

const service = useInterpret(
  someMachine,
  {
    actions: {
      /* ... */
    }
  },
  (state) => {
    // subscribes to state changes
    console.log(state.value);
  }
);

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

A Vue composition function (opens new window) that returns the selected value from the snapshot of an actor, such as a service. This hook will only cause a rerender if the selected value changes, as determined by the optional compare function.

Since 0.6.0

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.
  • getSnapshot (optional) - a function that should return the latest emitted value from the actor.
    • Defaults to attempting to get the actor.state, or returning undefined if that does not exist. Will automatically pull the state from services.
import { useSelector } from '@xstate/vue';

const props = defineProps(['service']);

const selectCount = (state) => state.context.count;

const count = useSelector(props.service, selectCount);

With compare function:

import { useSelector } from '@xstate/vue';

const props = defineProps(['service']);

const selectUser = (state) => state.context.user;
const compareUser = (prevUser, nextUser) => prevUser.id === nextUser.id;

const user = useSelector(props.service, selectUser, compareUser);

With useInterpret(...):

import { useInterpret, useSelector } from '@xstate/vue';
import { someMachine } from '../path/to/someMachine';

const selectCount = (state) => state.context.count;

const service = useInterpret(someMachine);
const count = useSelector(service, selectCount);

# 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 setup>
import { assign, createMachine } from 'xstate';
import { useMachine } from '@xstate/vue';

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: (_context, event) => event.data
          })
        },
        onError: {
          target: 'failure',
          actions: assign({
            error: (_context, event) => event.data
          })
        }
      }
    },
    success: {
      entry: 'notifySuccess',
      type: 'final'
    },
    failure: {
      on: {
        RETRY: 'loading'
      }
    }
  }
});

const props = defineProps({
  onResolve: {
    type: Function,
    default: () => {}
  }
});

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

<template>
  <template v-if="state.value === 'idle'">
    <button @click="send({ type: 'FETCH', query: 'something' })">
      Search for something
    </button>
  </template>

  <template v-else-if="state.value === 'loading'">
    <div>Searching...</div>
  </template>

  <template v-else-if="state.value === 'success'">
    <div>Success! {{ state.context.data }}</div>
  </template>

  <template v-else-if="state.value === 'failure'">
    <p>{{ state.context.error.message }}</p>
    <button @click="send('RETRY')">Retry</button>
  </template>
</template>

# Matching States

For hierarchical (opens new window) and parallel (opens new window) machines, the state values will be objects, not strings. In this case, it's better to use state.matches(...) (opens new window):

<template>
  <div>
    <loader-idle v-if="state.matches('idle')" />
    <loader-loading-user v-else-if="state.matches({ loading: 'user' })" />
    <loader-loading-friends v-else-if="state.matches({ loading: 'friends' })" />
  </div>
</template>

# Persisted and Rehydrated State

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

<script setup>
// 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
});
</script>

# Migration from 0.4.0

  • For spawned actors created using invoke or spawn(...), use the useActor() hook instead of useService():

    -import { useService } from '@xstate/vue';
    +import { useActor } from '@xstate/vue';
    
    -const {state, send} = useService(someActor);
    +const {state, send} = useActor(someActor);