Vize

use-machine

Finite state machine with typed states, events, and targets.

Package @vizejs/composable/use-machine
Own the source vize lib pull composable:use-machine
Runtime exports useMachine
Gzip budget 1536 B

Minimal setup

Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).

vp install @vizejs/composable

Import the entry directly. Call lifecycle-bound utilities inside <script setup> so cleanup follows the component scope; consult the runtime contract for this entry's SSR and hydration behavior.

import { useMachine } from "@vizejs/composable/use-machine";

Try it

Walk through a guarded checkout

Model a checkout as named steps so each screen only offers the actions that are valid there.

Open the interactive example

What to observe: The cart starts empty and only offers Add item. Add one item to unlock Go to shipping, then continue to Review order and Place order. Back returns one step; Start a new order empties the cart.

nextEvents lists the events the current state accepts and whose guards pass, so the buttons follow the machine. add is a self-transition whose action returns the next context. send() returns false for an event the state does not accept.

Compare the initial and interacted states
Walk through a guarded checkout: initial state
Initial state
Walk through a guarded checkout: state after the verified interactions
After the verified interactions

Copy the example

Save this complete SFC in your Vue project. Its public imports and source are the same ones executed in the preview.

<script setup lang="ts">
import { useMachine } from "@vizejs/composable/use-machine";

type Step = "cart" | "shipping" | "review" | "placed";
type Action = "add" | "checkout" | "back" | "ship" | "place" | "restart";

const { state, context, nextEvents, send, can } = useMachine<Step, Action, { items: number }>({
  initial: "cart",
  context: { items: 0 },
  states: {
    cart: {
      on: {
        add: { target: "cart", action: (order) => ({ items: order.items + 1 }) },
        checkout: { target: "shipping", guard: (order) => order.items > 0 },
      },
    },
    shipping: { on: { back: "cart", ship: "review" } },
    review: { on: { back: "shipping", place: "placed" } },
    placed: { on: { restart: { target: "cart", action: () => ({ items: 0 }) } } },
  },
});

const labels: Record<Action, string> = {
  add: "Add item",
  checkout: "Go to shipping",
  back: "Back",
  ship: "Review order",
  place: "Place order",
  restart: "Start a new order",
};
</script>

<template>
  <div class="composable-example">
    <p>Walk through a checkout that only moves forward when each step is ready.</p>
    <output aria-live="polite">Step: {{ state }} · Items: {{ context.items }}</output>
    <div class="example-actions">
      <button v-for="event in nextEvents" :key="event" type="button" @click="() => send(event)">
        {{ labels[event] }}
      </button>
    </div>
    <p v-if="state === 'cart' && !can('checkout')">Add an item before checking out.</p>
  </div>
</template>

Runtime contract

Utility Category Stability SSR Hydration Cleanup Targets Host globals Uses
useMachine state experimental safe stable none web, server, worker, native, desktop, terminal — —

API

useMachine

Finite state machine with typed states, events, and targets. State names are inferred from the keys of states, event names from the keys of every on map, and every target (and initial) must name a declared state, so typos fail to compile. Transitions may carry guards (first passing guard wins) and actions that return the next context; exit runs on the old state before entry runs on the new one, and a self-transition runs both. Synchronous and free of globals: identical on server and client, nothing to dispose.

function useMachine<State extends string, Event extends string = never, Context = undefined>( config: MachineConfig<State, Event, Context>, ): Machine<State, Event, Context>
const fetcher = useMachine({
  initial: "idle",
  context: { retries: 0 },
  states: {
    idle: { on: { FETCH: "loading" } },
    loading: { on: { RESOLVE: "success", REJECT: "failure" } },
    failure: {
      on: {
        RETRY: { target: "loading", guard: (c) => c.retries < 3, action: (c) => ({ retries: c.retries + 1 }) },
      },
    },
    success: {},
  },
});
fetcher.send("FETCH");

Types

MachineTransition

Guarded, effectful transition to target.

Member Type Description
target State State entered when the transition is taken.
guard? (context: Context) => boolean Take the transition only when this returns true.
action? (context: Context) => Context Produce the next context while transitioning.

MachineStateNode

One state of a MachineConfig.

Member Type Description
on? { readonly [Name in Event]?: | State | MachineTransition<State, Context> | readonly MachineTransition<State, Context>[]; } Transitions keyed by event: a target state name, one transition object, or a list of guarded transitions where the first passing guard wins.
entry? (context: Context) => void Called with the context after the machine enters this state.
exit? (context: Context) => void Called with the context before the machine leaves this state.

MachineConfig

Definition consumed by useMachine.

Member Type Description
initial NoInfer<State> State the machine starts (and resets) in.
context Context Initial extended state.
states { readonly [Name in State]: MachineStateNode<NoInfer<State>, Event, Context> } Every state, keyed by name.

Machine

Reactive machine returned by useMachine.

Member Type Description
state Readonly<ShallowRef<State>> Current state name.
context Readonly<ShallowRef<Context>> Current extended state.
nextEvents ComputedRef<Event[]> Events that would cause a transition from the current state right now.
send (event: Event) => boolean Deliver an event.
can (event: Event) => boolean Whether event would cause a transition right now (guards included).
matches (...states: readonly State[]) => boolean Whether the current state is one of states.
reset () => void Return to the initial state and context without running hooks.