React hooks and utilities for integrating XState finite state machines and statecharts into React applications
The core hooks provide the primary interface for integrating XState actors with React components. These hooks manage actor lifecycle and state synchronization automatically.
Creates and manages an XState actor with automatic React state synchronization. Returns the current snapshot, send function, and actor reference.
function useActor<TLogic extends AnyActorLogic>(
logic: TLogic,
options?: ActorOptions<TLogic> & {
[K in RequiredActorOptionsKeys<TLogic>]: unknown;
}
): [SnapshotFrom<TLogic>, Actor<TLogic>['send'], Actor<TLogic>];logic: The XState actor logic (machine, promise, observable, etc.)options: Optional actor configuration optionsA tuple containing:
snapshot: Current actor snapshot/statesend: Function to send events to the actoractorRef: The actor reference for direct accessimport { useActor } from "@xstate/react";
import { createMachine, assign } from "xstate";
const counterMachine = createMachine({
id: "counter",
initial: "idle",
context: { count: 0 },
states: {
idle: {
on: {
INCREMENT: {
actions: assign({ count: ({ context }) => context.count + 1 })
}
}
}
}
});
function Counter() {
const [state, send, actorRef] = useActor(counterMachine);
return (
<div>
<p>Count: {state.context.count}</p>
<button onClick={() => send({ type: "INCREMENT" })}>
Increment
</button>
</div>
);
}useSyncExternalStoreCreates an actor reference without automatic state subscription. Useful when you need the actor reference but don't want automatic re-renders.
function useActorRef<TLogic extends AnyActorLogic>(
machine: TLogic,
options?: ActorOptions<TLogic>,
observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)
): Actor<TLogic>;machine: The XState actor logicoptions: Optional actor configuration optionsobserverOrListener: Optional observer or listener function for state changesThe actor reference for sending events and accessing state.
import { useActorRef, useSelector } from "@xstate/react";
import { createMachine } from "xstate";
const timerMachine = createMachine({
id: "timer",
initial: "idle",
context: { elapsed: 0 },
states: {
idle: {
on: { START: "running" }
},
running: {
on: { STOP: "idle" }
}
}
});
function Timer() {
const actorRef = useActorRef(timerMachine);
const elapsed = useSelector(actorRef, (state) => state.context.elapsed);
const isRunning = useSelector(actorRef, (state) => state.matches("running"));
return (
<div>
<p>Elapsed: {elapsed}ms</p>
<button onClick={() => actorRef.send({ type: isRunning ? "STOP" : "START" })}>
{isRunning ? "Stop" : "Start"}
</button>
</div>
);
}Subscribes to a specific part of an actor's state using a selector function. Optimizes re-rendering by only updating when the selected value changes.
function useSelector<
TActor extends Pick<AnyActorRef, 'subscribe' | 'getSnapshot'> | undefined,
T
>(
actor: TActor,
selector: (
snapshot: TActor extends { getSnapshot(): infer TSnapshot } ? TSnapshot : undefined
) => T,
compare?: (a: T, b: T) => boolean
): T;actor: The actor reference to subscribe to (can be undefined)selector: Function to extract the desired value from the snapshotcompare: Optional comparison function (defaults to ===)The selected value from the actor's current snapshot.
import { useActorRef, useSelector } from "@xstate/react";
import { createMachine } from "xstate";
const appMachine = createMachine({
id: "app",
initial: "loading",
context: {
user: null,
posts: [],
error: null
},
states: {
loading: {
on: { LOADED: "idle" }
},
idle: {
on: { ERROR: "error" }
},
error: {}
}
});
function UserProfile() {
const appActor = useActorRef(appMachine);
// Only re-render when user changes
const user = useSelector(appActor, (state) => state.context.user);
// Only re-render when loading state changes
const isLoading = useSelector(appActor, (state) => state.matches("loading"));
// Custom comparison for complex objects
const posts = useSelector(
appActor,
(state) => state.context.posts,
(a, b) => a.length === b.length && a.every((post, i) => post.id === b[i].id)
);
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h1>{user?.name}</h1>
<p>{posts.length} posts</p>
</div>
);
}useSyncExternalStoreWithSelectorfunction ConditionalComponent({ shouldUseActor }: { shouldUseActor: boolean }) {
const [state, send] = useActor(
shouldUseActor ? myMachine : createMachine({ initial: "idle", states: { idle: {} } })
);
// Component logic...
}function Parent() {
const actorRef = useActorRef(sharedMachine);
return (
<div>
<Child1 actor={actorRef} />
<Child2 actor={actorRef} />
</div>
);
}
function Child1({ actor }: { actor: Actor<typeof sharedMachine> }) {
const value = useSelector(actor, (state) => state.context.someValue);
return <div>{value}</div>;
}// Use useSelector with custom comparison for expensive operations
const expensiveValue = useSelector(
actor,
(state) => computeExpensiveValue(state.context.data),
(a, b) => a.id === b.id // Only recompute when ID changes
);Install with Tessl CLI
npx tessl i tessl/npm-xstate--react