Stately
XState v6 alpha

Select actor data

Read actor data in Solid with fromActorRef and createMemo.

XState v6 is in alpha

APIs and behavior may change before the stable release.

@xstate/solid has no useSelector. It does not need one: fromActorRef(...) returns a deeply reactive snapshot, so reading one property is already a fine-grained subscription. Derive anything further with Solid's createMemo(...).

import { createMemo } from 'solid-js';
import { fromActorRef, useActorRef } from '@xstate/solid';

const actorRef = useActorRef(checkoutMachine);
const snapshot = fromActorRef(actorRef);

const total = createMemo(() => snapshot().context.total);

return <output>{total()}</output>;

fromActorRef

fromActorRef(actorRef) accepts an actor reference or an accessor of one, and returns an Accessor of that actor's snapshot.

ArgumentDescription
actorRefAn actor reference, an Accessor<ActorRef>, or undefined.

The returned accessor's value is a Solid store built by diffing each new snapshot into the previous one. Only the paths that changed notify their readers, so snapshot().context.progress re-runs on progress updates and snapshot().value does not.

The diff clones plain objects and arrays. Functions, class instances and other non-plain values are carried over by reference, which is what keeps snapshot methods such as matches(...), hasTag(...) and can(...) working on the store.

useActor(...) uses this internally: its first tuple member is fromActorRef(actorRef)().

Change detection

There is no compare argument, because there is nothing to compare: the store only notifies the paths that changed. A memo over the store adds Solid's own equality check on top.

const isPaying = createMemo(() => snapshot().matches('paying'));

isPaying() is false for many transitions in a row and only notifies its readers when the boolean flips.

For an object-shaped selection, give the memo an equality function:

const summary = createMemo(
  () => ({
    step: snapshot().value,
    itemCount: snapshot().context.items.length
  }),
  undefined,
  { equals: (a, b) => a.step === b.step && a.itemCount === b.itemCount }
);

Selecting from child actors

Read a child from snapshot().children and pass it to fromActorRef(...).

function UploadProgress(props: { parentRef: ActorRefFrom<typeof uploadMachine> }) {
  const parent = fromActorRef(props.parentRef);
  const uploadRef = createMemo(() => parent().children.upload);
  const upload = fromActorRef(uploadRef);

  return <progress value={upload()?.context.progress ?? 0} />;
}

Passing an accessor keeps this correct: children come and go with the states that invoke them, and fromActorRef(...) re-subscribes whenever the accessor returns a different actor. When the accessor returns undefined, the snapshot accessor returns undefined too and no subscription is made.

Warning: Destructuring a snapshot (const { context } = snapshot()) reads through the store once and loses reactivity. Keep property access inside JSX, memos and effects.

TypeScript

The snapshot type comes from the actor reference. When the actor may be undefined, so is the accessor's value.

import type { SnapshotFrom } from 'xstate';

const selectTotal = (snapshot: SnapshotFrom<typeof checkoutMachine>) =>
  snapshot.context.total;

const snapshot = fromActorRef(actorRef); // Accessor<SnapshotFrom<...>>
const total = createMemo(() => selectTotal(snapshot()));

Selectors cheatsheet

const snapshot = fromActorRef(actorRef);
const snapshot = fromActorRef(() => props.actorRef);

const count = createMemo(() => snapshot().context.count);
const isDone = createMemo(() => snapshot().matches('done'));

On this page