diff --git a/.changeset/fix-snapshot-type-5480.md b/.changeset/fix-snapshot-type-5480.md new file mode 100644 index 0000000000..da77a539a2 --- /dev/null +++ b/.changeset/fix-snapshot-type-5480.md @@ -0,0 +1,13 @@ +--- +'xstate': patch +'@xstate/react': patch +--- + +Fix `ActorOptions.snapshot` and `Actor.getPersistedSnapshot()` to return `SnapshotFrom` instead of `Snapshot`, so that rehydrating a machine from a persisted snapshot no longer produces TypeScript errors. + +```ts +const persisted = actor.getPersistedSnapshot(); // typed SnapshotFrom +useMachine(machine, { snapshot: persisted }); // now typechecks +``` + +Resolves [#5480](https://github.com/statelyai/xstate/issues/5480). diff --git a/packages/core/src/createActor.ts b/packages/core/src/createActor.ts index 41db36fd7a..7a5ee66ebe 100644 --- a/packages/core/src/createActor.ts +++ b/packages/core/src/createActor.ts @@ -784,9 +784,12 @@ export class Actor * Can be restored with {@link ActorOptions.state} * @see https://stately.ai/docs/persistence */ - public getPersistedSnapshot(): Snapshot; - public getPersistedSnapshot(options?: unknown): Snapshot { - return this.logic.getPersistedSnapshot(this._snapshot, options); + public getPersistedSnapshot(): SnapshotFrom; + public getPersistedSnapshot(options?: unknown): SnapshotFrom { + return this.logic.getPersistedSnapshot( + this._snapshot, + options + ) as SnapshotFrom; } public [symbolObservable](): InteropSubscribable> { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 070f61a11f..e9df4b9f46 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1865,10 +1865,10 @@ export interface ActorOptions { * Can be generated with {@link Actor.getPersistedSnapshot}. * @see https://stately.ai/docs/persistence */ - snapshot?: Snapshot; + snapshot?: SnapshotFrom; /** @deprecated Use `snapshot` instead. */ - state?: Snapshot; + state?: SnapshotFrom; /** The source actor logic. */ src?: string | AnyActorLogic; diff --git a/packages/xstate-react/test/createActorContext.test.tsx b/packages/xstate-react/test/createActorContext.test.tsx index a79a286627..2eb0aa2598 100644 --- a/packages/xstate-react/test/createActorContext.test.tsx +++ b/packages/xstate-react/test/createActorContext.test.tsx @@ -3,6 +3,7 @@ import { assign, fromPromise, Snapshot, + SnapshotFrom, InspectionEvent } from 'xstate'; import { fireEvent, screen, render, waitFor } from '@testing-library/react'; @@ -374,7 +375,7 @@ describe('createActorContext', () => { } }); const SomeContext = createActorContext(machine); - let persistedState: Snapshot | undefined = undefined; + let persistedState: SnapshotFrom | undefined = undefined; const Component = () => { const actorRef = SomeContext.useActorRef(); diff --git a/packages/xstate-react/test/types.test.tsx b/packages/xstate-react/test/types.test.tsx index 72d17df5bf..273b3f8818 100644 --- a/packages/xstate-react/test/types.test.tsx +++ b/packages/xstate-react/test/types.test.tsx @@ -1,5 +1,11 @@ import { render } from '@testing-library/react'; -import { ActorRefFrom, assign, createMachine, setup } from 'xstate'; +import { + ActorRefFrom, + assign, + createActor, + createMachine, + setup +} from 'xstate'; import { useActor, useActorRef, @@ -218,3 +224,29 @@ it('useMachine types work for machines with a specified id and state with an aft return state.matches('enabled'); } }); + +// https://github.com/statelyai/xstate/issues/5480 +it('useMachine accepts a rehydrated snapshot from getPersistedSnapshot #5480', () => { + const counterMachine = setup({}).createMachine({ + initial: 'idle', + states: { + idle: {}, + active: {} + } + }); + + // Round-trip: getPersistedSnapshot() -> useMachine(machine, { snapshot }) + // Both sides must typecheck without `as any` casts or @ts-expect-error. + function App() { + const actorRef = createActor(counterMachine).start(); + const persistedState = actorRef.getPersistedSnapshot(); + + const [, , restoredRef] = useMachine(counterMachine, { + snapshot: persistedState + }); + + return {restoredRef.getSnapshot().value}; + } + + render(); +}); diff --git a/packages/xstate-react/test/useActor.test.tsx b/packages/xstate-react/test/useActor.test.tsx index db9b2dba95..50f2c2da01 100644 --- a/packages/xstate-react/test/useActor.test.tsx +++ b/packages/xstate-react/test/useActor.test.tsx @@ -9,6 +9,7 @@ import { ActorRef, DoneActorEvent, Snapshot, + SnapshotFrom, StateFrom, assign, createActor, @@ -86,7 +87,7 @@ describeEachReactMode('useActor (%s)', ({ suiteKey, render }) => { const Fetcher: React.FC<{ onFetch: () => Promise; - persistedState?: Snapshot; + persistedState?: SnapshotFrom; }> = ({ onFetch = () => { return new Promise((res) => res('some data'));