diff --git a/packages/xstate-vue/src/useActor.ts b/packages/xstate-vue/src/useActor.ts index df03b5a786..0f6229a100 100644 --- a/packages/xstate-vue/src/useActor.ts +++ b/packages/xstate-vue/src/useActor.ts @@ -1,5 +1,11 @@ import isDevelopment from '#is-development'; -import { Ref } from 'vue'; +import { + effectScope, + getCurrentScope, + onScopeDispose, + Ref, + shallowRef +} from 'vue'; import { Actor, ActorOptions, @@ -11,7 +17,6 @@ import { type RequiredActorOptionsKeys } from 'xstate'; import { useActorRef } from './useActorRef.ts'; -import { useSelector } from './useSelector.ts'; export function useActor( actorLogic: TLogic, @@ -42,16 +47,26 @@ export function useActor( ); } - function listener(nextSnapshot: Snapshot) { - snapshot.value = nextSnapshot; - } + const scope = effectScope(); + + const result = scope.run(() => { + const snapshot = shallowRef(); - const actorRef = useActorRef(actorLogic, options, listener); - const snapshot = useSelector(actorRef, (s) => s); + function listener(nextSnapshot: Snapshot) { + snapshot.value = nextSnapshot; + } + + const actorRef = useActorRef(actorLogic, options, listener); + snapshot.value = actorRef.getSnapshot(); + return { snapshot, actorRef, send: actorRef.send }; + }); + + if (getCurrentScope()) { + onScopeDispose(() => { + scope.stop(); + }); + } - return { - snapshot, - send: actorRef.send, - actorRef: actorRef - }; + if (!result) throw new Error('useActor: effectScope did not run correctly'); + return result; } diff --git a/packages/xstate-vue/src/useActorRef.ts b/packages/xstate-vue/src/useActorRef.ts index 261f6bb606..bb672d1490 100644 --- a/packages/xstate-vue/src/useActorRef.ts +++ b/packages/xstate-vue/src/useActorRef.ts @@ -1,4 +1,4 @@ -import { onBeforeUnmount, onMounted } from 'vue'; +import { getCurrentScope, onScopeDispose } from 'vue'; import { Actor, ActorOptions, @@ -34,18 +34,18 @@ export function useActorRef( ): Actor { const actorRef = createActor(actorLogic, options); - let sub: Subscription; - onMounted(() => { - if (observerOrListener) { - sub = actorRef.subscribe(toObserver(observerOrListener)); - } - actorRef.start(); - }); + let sub: Subscription | undefined; + if (observerOrListener) { + sub = actorRef.subscribe(toObserver(observerOrListener)); + } + actorRef.start(); - onBeforeUnmount(() => { - actorRef.stop(); - sub?.unsubscribe(); - }); + if (getCurrentScope()) { + onScopeDispose(() => { + actorRef.stop(); + sub?.unsubscribe(); + }); + } return actorRef; } diff --git a/packages/xstate-vue/src/useSelector.ts b/packages/xstate-vue/src/useSelector.ts index 2e76b61b27..bb151c1b29 100644 --- a/packages/xstate-vue/src/useSelector.ts +++ b/packages/xstate-vue/src/useSelector.ts @@ -1,4 +1,11 @@ -import { Ref, isRef, shallowRef, watch } from 'vue'; +import { + Ref, + getCurrentScope, + isRef, + onScopeDispose, + shallowRef, + watch +} from 'vue'; import { AnyActorRef } from 'xstate'; function defaultCompare(a: T, b: T) { @@ -30,26 +37,29 @@ export function useSelector< } }; + const sub = actorRefRef.value?.subscribe({ + next: (emitted) => { + updateSelectedIfChanged(selector(emitted)); + }, + error: noop, + complete: noop + }); + watch( actorRefRef, - (newActor, _, onCleanup) => { - selected.value = selector(newActor?.getSnapshot()); - if (!newActor) { - return; + (nextActor) => { + if (nextActor) { + selected.value = selector(nextActor.getSnapshot()); } - const sub = newActor.subscribe({ - next: (emitted) => { - updateSelectedIfChanged(selector(emitted)); - }, - error: noop, - complete: noop - }); - onCleanup(() => sub.unsubscribe()); }, - { - immediate: true - } + { immediate: true } ); + if (getCurrentScope()) { + onScopeDispose(() => { + sub?.unsubscribe(); + }); + } + return selected; } diff --git a/packages/xstate-vue/test/UseActorRefWithObserver.vue b/packages/xstate-vue/test/UseActorRefWithObserver.vue index eb87bf8574..c49b4a3256 100644 --- a/packages/xstate-vue/test/UseActorRefWithObserver.vue +++ b/packages/xstate-vue/test/UseActorRefWithObserver.vue @@ -8,7 +8,7 @@ import { useActorRef } from '../src/index.ts'; import { createMachine } from 'xstate'; -import { defineComponent, ref } from 'vue'; +import { defineComponent, nextTick, ref } from 'vue'; const machine = createMachine({ initial: 'inactive', states: { @@ -28,7 +28,9 @@ const machine = createMachine({ export default defineComponent({ setup() { const actor = useActorRef(machine, {}, (nextState) => { - state.value = nextState.value; + nextTick(() => { + state.value = nextState.value; + }); }); const state = ref(actor.getSnapshot().value); diff --git a/packages/xstate-vue/vitest.config.mts b/packages/xstate-vue/vitest.config.mts index c432b60d92..98cffbdf2e 100644 --- a/packages/xstate-vue/vitest.config.mts +++ b/packages/xstate-vue/vitest.config.mts @@ -1,7 +1,7 @@ import vue from '@vitejs/plugin-vue'; import { defineProject } from 'vitest/config'; -export const include = ['test/vue.test.ts']; +export const include = ['test/*.test.ts']; export default defineProject({ plugins: [vue()],