Skip to main content

Overview

The useActor composition function creates an actor from XState logic and manages its lifecycle within a Vue component.
function useActor<TLogic extends AnyActorLogic>(
  logic: TLogic,
  options?: ActorOptions<TLogic>
): {
  snapshot: Ref<SnapshotFrom<TLogic>>;
  send: Actor<TLogic>['send'];
  actorRef: Actor<TLogic>;
}

Basic Usage

<script setup>
import { useActor } from '@xstate/vue';
import { createMachine } from 'xstate';

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

const { snapshot, send } = useActor(toggleMachine);
</script>

<template>
  <div>
    <p>State: {{ snapshot.value }}</p>
    <button @click="send({ type: 'TOGGLE' })">Toggle</button>
  </div>
</template>

With Options

Pass options to configure the actor:
<script setup>
import { useActor } from '@xstate/vue';
import { createMachine } from 'xstate';

const machine = createMachine({
  context: ({ input }) => ({
    userId: input.userId,
    data: null
  }),
  // ...
});

const { snapshot, send } = useActor(machine, {
  input: { userId: '123' },
  inspect: (event) => {
    console.log('Inspector:', event);
  }
});
</script>

Accessing the Actor Ref

The actorRef allows direct interaction with the underlying actor:
<script setup>
import { useActor } from '@xstate/vue';
import { onMounted, onUnmounted } from 'vue';
import { myMachine } from './myMachine';

const { snapshot, send, actorRef } = useActor(myMachine);

// Subscribe to state changes
let subscription;
onMounted(() => {
  subscription = actorRef.subscribe((state) => {
    console.log('State changed:', state);
  });
});

onUnmounted(() => {
  subscription?.unsubscribe();
});

// Get current snapshot at any time
const currentState = actorRef.getSnapshot();
</script>

Reactive Snapshot

The snapshot ref automatically updates when the actor’s state changes:
<script setup>
import { useActor } from '@xstate/vue';
import { computed } from 'vue';
import { fetchMachine } from './fetchMachine';

const { snapshot, send } = useActor(fetchMachine);

// Computed properties based on snapshot
const isLoading = computed(() => snapshot.value.matches('loading'));
const data = computed(() => snapshot.value.context.data);
const hasError = computed(() => snapshot.value.matches('error'));
</script>

<template>
  <div>
    <div v-if="isLoading">Loading...</div>
    <div v-else-if="hasError">Error occurred</div>
    <div v-else>{{ data }}</div>
  </div>
</template>

useMachine Alias

The useMachine function is an alias for useActor with the same API:
<script setup>
import { useMachine } from '@xstate/vue';
import { createMachine } from 'xstate';

const machine = createMachine({
  // ...
});

// useMachine and useActor are identical
const { snapshot, send, actorRef } = useMachine(machine);
</script>

Lifecycle Management

The actor lifecycle is automatically managed:
  1. Component Mount (onMounted)
    • Observer/listener subscriptions are created (if provided)
    • Actor is started via actorRef.start()
  2. Component Unmount (onBeforeUnmount)
    • Actor is stopped via actorRef.stop()
    • Subscriptions are cleaned up
<script setup>
import { useActor } from '@xstate/vue';
import { myMachine } from './myMachine';

// Actor automatically starts on mount
const { snapshot, send } = useActor(myMachine);

// Actor automatically stops on unmount
</script>

Error Handling

The useActor hook expects actor logic, not an existing actor ref. If you pass an actor ref, it will throw an error in development:
<script setup>
import { useActor } from '@xstate/vue';
import { createActor, createMachine } from 'xstate';

const machine = createMachine({/* ... */});
const actorRef = createActor(machine);

// ❌ This will throw an error
const { snapshot } = useActor(actorRef);

// ✅ Use useSelector instead for existing actors
import { useSelector } from '@xstate/vue';
const snapshot = useSelector(actorRef, (s) => s);
</script>

TypeScript

The hook is fully typed:
import { setup } from 'xstate';
import { useActor } from '@xstate/vue';

const machine = setup({
  types: {
    context: {} as { count: number; name: string },
    events: {} as 
      | { type: 'INCREMENT' }
      | { type: 'DECREMENT' }
      | { type: 'SET_NAME'; name: string }
  }
}).createMachine({
  context: { count: 0, name: '' },
  // ...
});

const { snapshot, send, actorRef } = useActor(machine);

// All types are inferred
snapshot.value.context.count; // number
snapshot.value.context.name; // string

// TypeScript validates events
send({ type: 'INCREMENT' }); // ✅
send({ type: 'SET_NAME', name: 'Alice' }); // ✅
send({ type: 'INVALID' }); // ❌ TypeScript error

useActorRef

Get actor ref without reactive snapshot

useSelector

Select derived values from snapshots