diff --git a/packages/vue/src/composables/useAppSharedState/index.test.ts b/packages/vue/src/composables/useAppSharedState/index.test.ts new file mode 100644 index 0000000..d3d4829 --- /dev/null +++ b/packages/vue/src/composables/useAppSharedState/index.test.ts @@ -0,0 +1,40 @@ +import { describe, it, vi, expect } from 'vitest'; +import { ref, reactive } from 'vue'; +import { useAppSharedState } from '.'; + +describe('useAppSharedState', () => { + it('should initialize state only once', () => { + const stateFactory = (initValue?: number) => { + const count = ref(initValue ?? 0); + return { count }; + }; + + const useSharedState = useAppSharedState(stateFactory); + + const state1 = useSharedState(1); + const state2 = useSharedState(2); + + expect(state1.count.value).toBe(1); + expect(state2.count.value).toBe(1); + expect(state1).toBe(state2); + }); + + it('should return the same state object across different calls', () => { + const stateFactory = () => { + const state = reactive({ count: 0 }); + const increment = () => state.count++; + return { state, increment }; + }; + + const useSharedState = useAppSharedState(stateFactory); + + const sharedState1 = useSharedState(); + const sharedState2 = useSharedState(); + + expect(sharedState1.state.count).toBe(0); + sharedState1.increment(); + expect(sharedState1.state.count).toBe(1); + expect(sharedState2.state.count).toBe(1); + expect(sharedState1).toBe(sharedState2); + }); +}); \ No newline at end of file diff --git a/packages/vue/src/composables/useAppSharedState/index.ts b/packages/vue/src/composables/useAppSharedState/index.ts new file mode 100644 index 0000000..321282c --- /dev/null +++ b/packages/vue/src/composables/useAppSharedState/index.ts @@ -0,0 +1,40 @@ +import type { AnyFunction } from '@robonen/stdlib'; +import { effectScope, onScopeDispose } from 'vue'; + +// TODO: maybe we should control subscriptions and dispose them when the child scope is disposed + +/** + * @name useAppSharedState + * @category State + * @description Provides a shared state object for use across Vue instances + * + * @param {Function} stateFactory The factory function to create the shared state + * @returns {Function} The shared state object + * + * @example + * const useSharedState = useAppSharedState((initValue?: number) => { + * const count = ref(initValue ?? 0); + * return { count }; + * }); + * + * @example + * const useSharedState = useAppSharedState(() => { + * const state = reactive({ count: 0 }); + * const increment = () => state.count++; + * return { state, increment }; + * }); + */ +export function useAppSharedState(stateFactory: Fn) { + let initialized = false; + let state: ReturnType; + const scope = effectScope(true); + + return ((...args: Parameters) => { + if (!initialized) { + state = scope.run(() => stateFactory(...args)); + initialized = true; + } + + return state; + }); +}