Skip to content

Fix #5480: narrow ActorOptions.snapshot and getPersistedSnapshot() to SnapshotFrom<TLogic> - #5547

Open
ther12k wants to merge 2 commits into
statelyai:mainfrom
ther12k:fix/issue-5480-snapshot-type
Open

Fix #5480: narrow ActorOptions.snapshot and getPersistedSnapshot() to SnapshotFrom<TLogic>#5547
ther12k wants to merge 2 commits into
statelyai:mainfrom
ther12k:fix/issue-5480-snapshot-type

Conversation

@ther12k

@ther12k ther12k commented Jun 22, 2026

Copy link
Copy Markdown

Closes #5480

Problem

ActorOptions.snapshot and Actor.getPersistedSnapshot() were typed
Snapshot<unknown> (a 4-shape union of status / output /
error). Real machine snapshots (MachineSnapshot<...>) have a
richer shape (status / value / context / matches(...) /
...). The two shapes do not overlap, so the round-trip
getPersistedSnapshot()useMachine(machine, { snapshot })
failed typecheck even though the runtime is fine.

The deprecated state alias had the same problem.

Fix

Narrow both sides of the round-trip to SnapshotFrom<TLogic> so
the types agree:

  • packages/core/src/types.tsActorOptions.snapshot and
    ActorOptions.state now use SnapshotFrom<TLogic> instead of
    Snapshot<unknown>.
  • packages/core/src/createActor.ts
    Actor.getPersistedSnapshot() return type is now
    SnapshotFrom<TLogic>. The internal
    this.logic.getPersistedSnapshot(...) call still returns
    Snapshot<unknown> per ActorLogic's generic declaration, so
    a single cast is applied at the public boundary. Runtime
    behaviour is unchanged.

For a StateMachine, SnapshotFrom<TMachine> resolves to
MachineSnapshot<...> via ReturnType<R['transition']>, which
is exactly what getPersistedSnapshot() returns at runtime.

Test

packages/xstate-react/test/types.test.tsx gets a new
it('useMachine accepts a rehydrated snapshot from getPersistedSnapshot #5480', ...) that exercises the round-trip
end-to-end and is expected to typecheck without as any or
@ts-expect-error. Two existing tests
(createActorContext.test.tsx, useActor.test.tsx) are updated
to use SnapshotFrom<typeof machine> for their persistedState
annotations to keep tsc green.

Verification

  • pnpm typecheck → 0 errors (3 errors before the test-file
    annotations were updated; now clean).
  • pnpm test:core is gated on Node ≥ 22 in this repo (uses
    Promise.withResolvers) — environment issue unrelated to this
    change.

Backward-compatible: callers passing a hand-written
Snapshot<unknown> to snapshot: are unaffected because the
internal _initState / restoreSnapshot flow still accepts any
shape that the logic's restoreSnapshot can convert.

…SnapshotFrom<TLogic>

Closes statelyai#5480.

Both ActorOptions.snapshot and Actor.getPersistedSnapshot() were
typed Snapshot<unknown> (a 4-shape union of status / output /
error). Real machine snapshots (MachineSnapshot<...>) have a
richer shape with value / context / matches() and do not overlap
with Snapshot<unknown>, so the round-trip getPersistedSnapshot()
-> useMachine(machine, { snapshot }) failed typecheck even though
the runtime was fine.

Narrow both sides to SnapshotFrom<TLogic> so the types agree.
For StateMachine, SnapshotFrom<TMachine> resolves to
MachineSnapshot<...> via ReturnType<R['transition']>, which is
exactly what getPersistedSnapshot() returns at runtime.

- packages/core/src/types.ts: ActorOptions.snapshot and
  ActorOptions.state use SnapshotFrom<TLogic>.
- packages/core/src/createActor.ts: Actor.getPersistedSnapshot()
  return is SnapshotFrom<TLogic>. The internal
  this.logic.getPersistedSnapshot(...) call still returns
  Snapshot<unknown> per ActorLogic's generic declaration, so a
  single cast is applied at the public boundary. Runtime
  behaviour is unchanged.

Backward-compatible: callers passing a hand-written
Snapshot<unknown> are unaffected because the internal _initState
/ restoreSnapshot flow still accepts any shape that the logic's
restoreSnapshot can convert.

Adds a regression test in
packages/xstate-react/test/types.test.tsx (statelyai#5480) that
exercises the round-trip end-to-end without `as any` or
`@ts-expect-error`. Two existing tests
(createActorContext.test.tsx, useActor.test.tsx) updated to use
SnapshotFrom<typeof machine> for their persistedState
annotations to keep tsc green.
@changeset-bot

changeset-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ab963e3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
xstate Patch
@xstate/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Snapshot type for useMachine second argument is wrong

2 participants