diff --git a/README.md b/README.md index 913633fd8f..5ce99ad5e6 100644 --- a/README.md +++ b/README.md @@ -181,15 +181,15 @@ Read [📽 the slides](http://slides.com/davidkhourshid/finite-state-machines) ( ## Packages -| Package | Description | -| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -| 🤖 `xstate` | Core finite state machine and statecharts library + interpreter | -| [📉 `@xstate/graph`](https://github.com/statelyai/xstate/tree/main/packages/xstate-graph) | Graph traversal and model-based testing utilities using XState | -| [⚛️ `@xstate/react`](https://github.com/statelyai/xstate/tree/main/packages/xstate-react) | React hooks and utilities for using XState in React applications | -| [💚 `@xstate/vue`](https://github.com/statelyai/xstate/tree/main/packages/xstate-vue) | Vue composition functions and utilities for using XState in Vue applications | -| [🎷 `@xstate/svelte`](https://github.com/statelyai/xstate/tree/main/packages/xstate-svelte) | Svelte utilities for using XState in Svelte applications | -| [🥏 `@xstate/solid`](https://github.com/statelyai/xstate/tree/main/packages/xstate-solid) | Solid hooks and utilities for using XState in Solid applications | -| [🔍 `@statelyai/inspect`](https://github.com/statelyai/inspect) | Inspection utilities for XState | +| Package | Description | +| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| 🤖 `xstate` | Core finite state machine and statecharts library + interpreter | +| [📉 `@xstate/graph`](https://github.com/statelyai/xstate/tree/main/packages/xstate-graph) | Graph traversal and model-based testing utilities using XState | +| [⚛️ `@xstate/react`](https://github.com/statelyai/xstate/tree/main/packages/xstate-react) | React hooks and utilities for using XState in React applications | +| [💚 `@xstate/vue`](https://github.com/statelyai/xstate/tree/main/packages/xstate-vue) | Vue composition functions and utilities for using XState in Vue applications | +| [🎷 `@xstate/svelte`](https://github.com/statelyai/xstate/tree/main/packages/xstate-svelte) | Svelte utilities for using XState in Svelte applications | +| [🥏 `@xstate/solid`](https://github.com/statelyai/xstate/tree/main/packages/xstate-solid) | Solid hooks and utilities for using XState in Solid applications | +| [🔍 `@statelyai/inspect`](https://github.com/statelyai/inspect) | Inspection utilities for XState | ## Finite State Machines diff --git a/packages/xstate-react/src/useActor.ts b/packages/xstate-react/src/useActor.ts index 277438658b..e73241bd9b 100644 --- a/packages/xstate-react/src/useActor.ts +++ b/packages/xstate-react/src/useActor.ts @@ -5,6 +5,7 @@ import { Actor, ActorOptions, AnyActorLogic, + Snapshot, SnapshotFrom, type ConditionalRequired, type IsNotNever, @@ -43,7 +44,10 @@ export function useActor( const subscribe = useCallback( (handleStoreChange: () => void) => { - const { unsubscribe } = actorRef.subscribe(handleStoreChange); + const { unsubscribe } = actorRef.subscribe( + handleStoreChange, + handleStoreChange + ); return unsubscribe; }, [actorRef] @@ -55,6 +59,10 @@ export function useActor( getSnapshot ); + if ((actorSnapshot as Snapshot).status === 'error') { + throw (actorSnapshot as Snapshot).error; + } + useEffect(() => { actorRef.start(); diff --git a/packages/xstate-react/src/useActorRef.ts b/packages/xstate-react/src/useActorRef.ts index 10a5d03873..d4b2abcb2c 100644 --- a/packages/xstate-react/src/useActorRef.ts +++ b/packages/xstate-react/src/useActorRef.ts @@ -6,6 +6,7 @@ import { AnyActorLogic, AnyStateMachine, Observer, + Snapshot, SnapshotFrom, createActor, toObserver, @@ -52,6 +53,8 @@ export function useIdleActorRef( return actorRef; } +const UNIQUE = {}; + export function useActorRef( machine: TLogic, ...[options, observerOrListener]: IsNotNever< @@ -73,12 +76,23 @@ export function useActorRef( ] ): Actor { const actorRef = useIdleActorRef(machine, options); + const [reactError, setReactError] = useState(() => { + const initialSnapshot: Snapshot = actorRef.getSnapshot(); + return initialSnapshot.status === 'error' ? initialSnapshot.error : UNIQUE; + }); + + if (reactError !== UNIQUE) { + throw reactError; + } useEffect(() => { - if (!observerOrListener) { - return; - } - let sub = actorRef.subscribe(toObserver(observerOrListener)); + const observer = toObserver(observerOrListener); + const errorListener = observer.error; + observer.error = (error) => { + setReactError(error); + errorListener?.(error); + }; + let sub = actorRef.subscribe(observer); return () => { sub.unsubscribe(); }; diff --git a/packages/xstate-react/src/useSelector.ts b/packages/xstate-react/src/useSelector.ts index da73946f65..2ebcbbf52f 100644 --- a/packages/xstate-react/src/useSelector.ts +++ b/packages/xstate-react/src/useSelector.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector'; -import { AnyActorRef, SnapshotFrom } from 'xstate'; +import { AnyActorRef, Snapshot, SnapshotFrom } from 'xstate'; type SyncExternalStoreSubscribe = Parameters< typeof useSyncExternalStoreWithSelector @@ -27,19 +27,31 @@ export function useSelector< if (!actor) { return () => {}; } - const { unsubscribe } = actor.subscribe(handleStoreChange); + const { unsubscribe } = actor.subscribe( + handleStoreChange, + handleStoreChange + ); return unsubscribe; }, [actor] ); const boundGetSnapshot = useCallback(() => actor?.getSnapshot(), [actor]); + const boundSelector: typeof selector = useCallback( + (snapshot: Snapshot | undefined) => { + if (snapshot?.status === 'error') { + throw snapshot.error; + } + return selector(snapshot as never); + }, + [selector] + ); const selectedSnapshot = useSyncExternalStoreWithSelector( subscribe, boundGetSnapshot, boundGetSnapshot, - selector, + boundSelector, compare );