@blac/react 2.0.0-rc-3 → 2.0.0-rc-4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const x=require("@blac/core"),n=require("react"),I=(d,t)=>{const{id:f,props:S,selector:b}=t??{},r=n.useMemo(()=>crypto.randomUUID(),[]),h=d.isolated?r:f,i=n.useRef(new Set),o=n.useRef(new Set),a=n.useCallback(()=>x.Blac.getBloc(d,{id:h,props:S,instanceRef:r}),[d,S]),e=n.useRef(a()),l=n.useMemo(()=>c=>{const u=e.current;if(!u)return[];if(b)return b(c);if(u.defaultDependencySelector)return u.defaultDependencySelector(c);if(typeof c!="object")return[[c]];const v=[];for(const y of i.current)y in c&&v.push(c[y]);const g=[];for(const y of o.current)if(y in u)try{const m=u[y];switch(typeof m){case"function":continue;default:g.push(m);continue}}catch(m){x.Blac.instance.log("useBloc Error",m)}return[v,g]},[]),M=n.useMemo(()=>({subscribe:c=>{const u={fn:()=>{try{i.current=new Set,o.current=new Set,c(e.current.state)}catch(g){console.error({e:g,blocInstance:e,dependencyArray:l})}},dependencyArray:l,id:r};x.Blac.activateBloc(e.current);const v=e.current._observer.subscribe(u);return()=>{v()}},getSnapshot:()=>e.current.state,getServerSnapshot:()=>e.current.state}),[]);return{usedKeys:i,usedClassPropKeys:o,externalStore:M,instance:e,rid:r}},C=I;function E(d,t){var o;const{externalStore:f,usedKeys:S,usedClassPropKeys:b,instance:r,rid:B}=C(d,t),s=n.useSyncExternalStore(f.subscribe,f.getSnapshot,f.getServerSnapshot),h=n.useMemo(()=>typeof s=="object"?new Proxy(s,{get(a,e){return S.current.add(e),s[e]}}):s,[s]),i=n.useMemo(()=>new Proxy(r.current,{get(a,e){if(!r.current)return null;const l=r.current[e];return typeof l!="function"&&b.current.add(e),l}}),[(o=r.current)==null?void 0:o.uid]);return n.useEffect(()=>{var a;return r.current._addConsumer(B),(a=t==null?void 0:t.onMount)==null||a.call(t,r.current),()=>{var e;r.current&&((e=t==null?void 0:t.onUnmount)==null||e.call(t,r.current),r.current._removeConsumer(B))}},[r.current,B]),[h,i]}exports.externalBlocStore=C;exports.useBloc=E;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react"),I=require("@blac/core"),M=(y,c)=>{var t;const{id:S,props:h,selector:v}=c??{},s=u.useMemo(()=>crypto.randomUUID(),[]),m=y.isolated?s:S,f=u.useRef(new Set),b=u.useRef(new Set),C=u.useCallback(()=>I.Blac.getBloc(y,{id:m,props:h,instanceRef:s}),[y,h]),o=u.useRef(C()),g=u.useMemo(()=>e=>{const n=o.current;if(!n)return[];if(v)return v(e);if(n.defaultDependencySelector)return n.defaultDependencySelector(e);if(typeof e!="object")return[[e]];const i=[];for(const l of f.current)l in e&&i.push(e[l]);const x=[];for(const l of b.current)if(l in n)try{const d=n[l];switch(typeof d){case"function":continue;default:x.push(d);continue}}catch(d){I.Blac.instance.log("useBloc Error",d)}return[i,x]},[]),r=u.useMemo(()=>({subscribe:e=>{const n=o.current;if(!n)return()=>{};let i=!1;const x={fn:()=>{try{i||(i=!0,f.current=new Set,b.current=new Set,i=!1),e(n.state)}catch(d){console.error({e:d,blocInstance:n,dependencyArray:g})}},dependencyArray:g,id:s};I.Blac.activateBloc(n);const l=n._observer.subscribe(x);return()=>{l()}},getSnapshot:()=>{var e;return((e=o.current)==null?void 0:e.state)||{}},getServerSnapshot:()=>{var e;return((e=o.current)==null?void 0:e.state)||{}}}),[(t=o.current)==null?void 0:t.uid]);return{usedKeys:f,usedClassPropKeys:b,externalStore:r,instance:o,rid:s}};function P(y,c){var o,g;const{externalStore:S,usedKeys:h,usedClassPropKeys:v,instance:s,rid:B}=M(y,c),a=u.useSyncExternalStore(S.subscribe,S.getSnapshot,S.getServerSnapshot),m=u.useRef(new WeakMap),f=u.useRef(new WeakMap),b=u.useMemo(()=>{if(typeof a!="object"||a===null)return a;let r=m.current.get(a);return r||(r=new Proxy(a,{get(t,e){return h.current.add(e),t[e]},has(t,e){return e in t},ownKeys(t){return Reflect.ownKeys(t)},getOwnPropertyDescriptor(t,e){return Reflect.getOwnPropertyDescriptor(t,e)}}),m.current.set(a,r)),r},[a]),C=u.useMemo(()=>{if(!s.current)return null;let r=f.current.get(s.current);return r||(r=new Proxy(s.current,{get(t,e){if(!t)return null;const n=t[e];return typeof n!="function"&&v.current.add(e),n}}),f.current.set(s.current,r)),r},[(o=s.current)==null?void 0:o.uid]);return u.useEffect(()=>{var t;const r=s.current;if(r)return r._addConsumer(B),(t=c==null?void 0:c.onMount)==null||t.call(c,r),()=>{var e;r&&((e=c==null?void 0:c.onUnmount)==null||e.call(c,r),r._removeConsumer(B))}},[(g=s.current)==null?void 0:g.uid,B]),[b,C]}exports.externalBlocStore=M;exports.useBloc=P;
2
2
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/externalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState } from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\nimport { BlocHookOptions } from './useBloc';\n\nexport interface ExternalStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n /**\n * Subscribes to changes in the store and returns an unsubscribe function.\n * @param onStoreChange - Callback function that will be called whenever the store changes\n * @returns A function that can be called to unsubscribe from store changes\n */\n subscribe: (onStoreChange: (state: BlocState<InstanceType<B>>) => void) => () => void;\n\n /**\n * Gets the current snapshot of the store state.\n * @returns The current state of the store\n */\n getSnapshot: () => BlocState<InstanceType<B>>;\n\n /**\n * Gets the server snapshot of the store state.\n * This is optional and defaults to the same value as getSnapshot.\n * @returns The server state of the store\n */\n getServerSnapshot?: () => BlocState<InstanceType<B>>;\n}\n\nexport interface ExternalBlacStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n usedKeys: React.RefObject<Set<string>>;\n usedClassPropKeys: React.RefObject<Set<string>>;\n externalStore: ExternalStore<B>;\n instance: React.RefObject<InstanceType<B>>;\n rid: string;\n}\n\n/**\n * Creates an external store that wraps a Bloc instance, providing a React-compatible interface\n * for subscribing to and accessing bloc state.\n * \n * @template B - The type of the Bloc instance\n * @template S - The type of the Bloc state\n * @param bloc - The Bloc instance to wrap\n * @param dependencyArray - Function that returns an array of dependencies for the subscription\n * @param rid - Unique identifier for the subscription\n * @returns An ExternalStore instance that provides methods to subscribe to and access bloc state\n */\nconst useExternalBlocStore = <\n B extends BlocConstructor<BlocBase<any>>\n>(\n bloc: B,\n options: BlocHookOptions<InstanceType<B>> | undefined,\n): ExternalBlacStore<B> => {\n const { id: blocId, props, selector } = options ?? {};\n\n const rid = useMemo(() => {\n return crypto.randomUUID();\n }, []);\n\n const base = bloc as unknown as BlocBaseAbstract;\n\n const isIsolated = base.isolated;\n const effectiveBlocId = isIsolated ? rid : blocId;\n\n const usedKeys = useRef<Set<string>>(new Set());\n const usedClassPropKeys = useRef<Set<string>>(new Set());\n\n const getBloc = useCallback(() => {\n return Blac.getBloc(bloc, {\n id: effectiveBlocId,\n props,\n instanceRef: rid\n });\n }, [bloc, props]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n\n const dependencyArray: BlocHookDependencyArrayFn<BlocState<InstanceType<B>>> =\n useMemo(\n () =>\n (newState): ReturnType<BlocHookDependencyArrayFn<BlocState<InstanceType<B>>>> => {\n const instance = blocInstance.current;\n\n if (!instance) {\n return [];\n }\n\n // Use custom dependency selector if provided\n if (selector) {\n return selector(newState);\n }\n\n // Fall back to bloc's default dependency selector if available\n if (instance.defaultDependencySelector) {\n return instance.defaultDependencySelector(newState);\n }\n\n // For primitive states, use default selector\n if (typeof newState !== 'object') {\n // Default behavior for primitive states: re-render if the state itself changes.\n return [[newState]];\n }\n\n // For object states, track which properties were actually used\n const usedStateValues: string[] = [];\n for (const key of usedKeys.current) {\n if (key in newState) {\n usedStateValues.push(newState[key as keyof typeof newState]);\n }\n }\n\n // Track used class properties for dependency tracking, this enables rerenders when class getters change\n const usedClassValues: unknown[] = [];\n for (const key of usedClassPropKeys.current) {\n if (key in instance) {\n try {\n const value = instance[key as keyof InstanceType<B>];\n switch (typeof value) {\n case 'function':\n continue;\n default:\n usedClassValues.push(value);\n continue;\n }\n } catch (error) {\n Blac.instance.log('useBloc Error', error);\n }\n }\n }\n\n return [usedStateValues, usedClassValues];\n },\n [],\n );\n\n const state: ExternalStore<B> = useMemo(() => {\n return {\n subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {\n const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n\n listener(blocInstance.current.state);\n } catch (e) {\n // Log any errors that occur during the listener callback\n // This ensures errors in listeners don't break the entire application\n console.error({\n e,\n blocInstance,\n dependencyArray,\n });\n }\n },\n // Pass the dependency array to control when the subscription is updated\n dependencyArray,\n // Use the provided id to identify this subscription\n id: rid,\n }\n\n Blac.activateBloc(blocInstance.current);\n\n // Subscribe to the bloc's observer with the provided listener function\n // This will trigger the callback whenever the bloc's state changes\n const unSub = blocInstance.current._observer.subscribe(observer);\n\n // Return an unsubscribe function that can be called to clean up the subscription\n return () => {\n unSub();\n };\n },\n // Return an immutable snapshot of the current bloc state\n getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current.state,\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current.state,\n }\n }, []);\n\n return {\n usedKeys,\n usedClassPropKeys,\n externalStore: state,\n instance: blocInstance,\n rid,\n };\n};\n\nexport default useExternalBlocStore;\n","import {\n BlocBase,\n BlocConstructor,\n BlocHookDependencyArrayFn,\n BlocState,\n InferPropsFromGeneric,\n} from '@blac/core';\nimport {\n useEffect,\n useMemo,\n useSyncExternalStore\n} from 'react';\nimport useExternalBlocStore from './externalBlocStore';\n\n/**\n * Type definition for the return type of the useBloc hook\n * @template B - Bloc constructor type\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * Configuration options for the useBloc hook\n */\nexport interface BlocHookOptions<B extends BlocBase<any>> {\n id?: string;\n selector?: BlocHookDependencyArrayFn<BlocState<B>>;\n props?: InferPropsFromGeneric<B>;\n onMount?: (bloc: B) => void;\n onUnmount?: (bloc: B) => void;\n}\n\n/**\n * Default dependency selector that wraps the entire state in an array\n * @template T - State type\n * @param {T} s - Current state\n * @returns {Array<Array<T>>} Dependency array containing the entire state\n */\n\nconst log = (...args: unknown[]) => {\n console.log('useBloc', ...args);\n};\n\n/**\n * React hook for integrating with Blac state management\n *\n * This hook connects a React component to a Bloc state container, providing\n * automatic re-rendering when relevant state changes, dependency tracking,\n * and proper lifecycle management.\n *\n * @template B - Bloc constructor type\n * @template O - BlocHookOptions type\n * @param {B} bloc - Bloc constructor class\n * @param {O} [options] - Configuration options for the hook\n * @returns {HookTypes<B>} Tuple containing [state, bloc instance]\n *\n * @example\n * const [state, counterBloc] = useBloc(CounterBloc);\n * // Access state\n * console.log(state.count);\n * // Call bloc methods\n * counterBloc.increment();\n */\nexport default function useBloc<B extends BlocConstructor<BlocBase<any>>>(\n bloc: B,\n options?: BlocHookOptions<InstanceType<B>>,\n): HookTypes<B> {\n // Determine ID for isolated or shared blocs\n // const base = bloc as unknown as BlocBaseAbstract;\n // const isIsolated = base.isolated;\n // const effectiveBlocId = isIsolated ? rid : blocId;\n const {\n externalStore,\n usedKeys,\n usedClassPropKeys,\n instance,\n rid,\n } = useExternalBlocStore(bloc, options);\n\n // Subscribe to state changes using React's external store API\n const state = useSyncExternalStore<BlocState<InstanceType<B>>>(\n externalStore.subscribe,\n externalStore.getSnapshot,\n externalStore.getServerSnapshot,\n );\n\n const returnState = useMemo(() => {\n return typeof state === 'object'\n ? new Proxy(state, {\n get(_, prop) {\n usedKeys.current.add(prop as string);\n const value = state[prop as keyof typeof state];\n return value;\n },\n })\n : state;\n }, [state]);\n\n const returnClass = useMemo(() => {\n return new Proxy(instance.current, {\n get(_, prop) {\n if (!instance.current) {\n return null;\n }\n const value = instance.current[prop as keyof InstanceType<B>];\n if (typeof value !== 'function') {\n usedClassPropKeys.current.add(prop as string);\n }\n return value;\n },\n });\n }, [instance.current?.uid]);\n\n // Set up bloc lifecycle management\n useEffect(() => {\n instance.current._addConsumer(rid);\n\n // Call onMount callback if provided\n options?.onMount?.(instance.current);\n\n // Cleanup: remove this component as a consumer using the captured instance\n return () => {\n if (!instance.current) {\n return;\n }\n options?.onUnmount?.(instance.current);\n instance.current._removeConsumer(rid);\n };\n }, [instance.current, rid]); // Do not add options.onMount to deps, it will cause a loop\n\n return [returnState, returnClass];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","observer","e","unSub","useExternalBlocStore$1","useBloc","externalStore","useSyncExternalStore","returnState","_","prop","returnClass","_a","useEffect"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/useExternalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState } from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\nimport { BlocHookOptions } from './useBloc';\n\nexport interface ExternalStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n /**\n * Subscribes to changes in the store and returns an unsubscribe function.\n * @param onStoreChange - Callback function that will be called whenever the store changes\n * @returns A function that can be called to unsubscribe from store changes\n */\n subscribe: (onStoreChange: (state: BlocState<InstanceType<B>>) => void) => () => void;\n\n /**\n * Gets the current snapshot of the store state.\n * @returns The current state of the store\n */\n getSnapshot: () => BlocState<InstanceType<B>>;\n\n /**\n * Gets the server snapshot of the store state.\n * This is optional and defaults to the same value as getSnapshot.\n * @returns The server state of the store\n */\n getServerSnapshot?: () => BlocState<InstanceType<B>>;\n}\n\nexport interface ExternalBlacStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n usedKeys: React.RefObject<Set<string>>;\n usedClassPropKeys: React.RefObject<Set<string>>;\n externalStore: ExternalStore<B>;\n instance: React.RefObject<InstanceType<B>>;\n rid: string;\n}\n\n/**\n * Creates an external store that wraps a Bloc instance, providing a React-compatible interface\n * for subscribing to and accessing bloc state.\n */\nconst useExternalBlocStore = <\n B extends BlocConstructor<BlocBase<any>>\n>(\n bloc: B,\n options: BlocHookOptions<InstanceType<B>> | undefined,\n): ExternalBlacStore<B> => {\n const { id: blocId, props, selector } = options ?? {};\n\n const rid = useMemo(() => {\n return crypto.randomUUID();\n }, []);\n\n const base = bloc as unknown as BlocBaseAbstract;\n\n const isIsolated = base.isolated;\n const effectiveBlocId = isIsolated ? rid : blocId;\n\n const usedKeys = useRef<Set<string>>(new Set());\n const usedClassPropKeys = useRef<Set<string>>(new Set());\n\n const getBloc = useCallback(() => {\n return Blac.getBloc(bloc, {\n id: effectiveBlocId,\n props,\n instanceRef: rid\n });\n }, [bloc, props]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n\n const dependencyArray: BlocHookDependencyArrayFn<BlocState<InstanceType<B>>> =\n useMemo(\n () =>\n (newState): ReturnType<BlocHookDependencyArrayFn<BlocState<InstanceType<B>>>> => {\n const instance = blocInstance.current;\n\n if (!instance) {\n return [];\n }\n\n // Use custom dependency selector if provided\n if (selector) {\n return selector(newState);\n }\n\n // Fall back to bloc's default dependency selector if available\n if (instance.defaultDependencySelector) {\n return instance.defaultDependencySelector(newState);\n }\n\n // For primitive states, use default selector\n if (typeof newState !== 'object') {\n // Default behavior for primitive states: re-render if the state itself changes.\n return [[newState]];\n }\n\n // For object states, track which properties were actually used\n const usedStateValues: string[] = [];\n for (const key of usedKeys.current) {\n if (key in newState) {\n usedStateValues.push(newState[key as keyof typeof newState]);\n }\n }\n\n // Track used class properties for dependency tracking, this enables rerenders when class getters change\n const usedClassValues: unknown[] = [];\n for (const key of usedClassPropKeys.current) {\n if (key in instance) {\n try {\n const value = instance[key as keyof InstanceType<B>];\n switch (typeof value) {\n case 'function':\n continue;\n default:\n usedClassValues.push(value);\n continue;\n }\n } catch (error) {\n Blac.instance.log('useBloc Error', error);\n }\n }\n }\n\n return [usedStateValues, usedClassValues];\n },\n [],\n );\n\n const state: ExternalStore<B> = useMemo(() => {\n return {\n subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {\n const currentInstance = blocInstance.current;\n if (!currentInstance) {\n return () => {}; // Return no-op if no instance\n }\n\n // Use a flag to prevent multiple resets during the same listener execution\n let isResetting = false;\n\n const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n if (!isResetting) {\n isResetting = true;\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n isResetting = false;\n }\n\n listener(currentInstance.state);\n } catch (e) {\n // Log any errors that occur during the listener callback\n // This ensures errors in listeners don't break the entire application\n console.error({\n e,\n blocInstance: currentInstance,\n dependencyArray,\n });\n }\n },\n // Pass the dependency array to control when the subscription is updated\n dependencyArray,\n // Use the provided id to identify this subscription\n id: rid,\n }\n\n Blac.activateBloc(currentInstance);\n\n // Subscribe to the bloc's observer with the provided listener function\n // This will trigger the callback whenever the bloc's state changes\n const unSub = currentInstance._observer.subscribe(observer);\n\n // Return an unsubscribe function that can be called to clean up the subscription\n return () => {\n unSub();\n };\n },\n // Return an immutable snapshot of the current bloc state\n getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),\n }\n }, [blocInstance.current?.uid]); // Re-create store when instance changes\n\n return {\n usedKeys,\n usedClassPropKeys,\n externalStore: state,\n instance: blocInstance,\n rid,\n };\n};\n\nexport default useExternalBlocStore;\n","import {\n BlocBase,\n BlocConstructor,\n BlocHookDependencyArrayFn,\n BlocState,\n InferPropsFromGeneric,\n} from '@blac/core';\nimport {\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore\n} from 'react';\nimport useExternalBlocStore from './useExternalBlocStore';\n\n/**\n * Type definition for the return type of the useBloc hook\n * @template B - Bloc constructor type\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * Configuration options for the useBloc hook\n */\nexport interface BlocHookOptions<B extends BlocBase<any>> {\n id?: string;\n selector?: BlocHookDependencyArrayFn<BlocState<B>>;\n props?: InferPropsFromGeneric<B>;\n onMount?: (bloc: B) => void;\n onUnmount?: (bloc: B) => void;\n}\n\n/**\n * Default dependency selector that wraps the entire state in an array\n * @template T - State type\n * @param {T} s - Current state\n * @returns {Array<Array<T>>} Dependency array containing the entire state\n */\n\nconst log = (...args: unknown[]) => {\n console.log('useBloc', ...args);\n};\n\n/**\n * React hook for integrating with Blac state management\n *\n * This hook connects a React component to a Bloc state container, providing\n * automatic re-rendering when relevant state changes, dependency tracking,\n * and proper lifecycle management.\n *\n * @template B - Bloc constructor type\n * @template O - BlocHookOptions type\n * @param {B} bloc - Bloc constructor class\n * @param {O} [options] - Configuration options for the hook\n * @returns {HookTypes<B>} Tuple containing [state, bloc instance]\n *\n * @example\n * const [state, counterBloc] = useBloc(CounterBloc);\n * // Access state\n * console.log(state.count);\n * // Call bloc methods\n * counterBloc.increment();\n */\nexport default function useBloc<B extends BlocConstructor<BlocBase<any>>>(\n bloc: B,\n options?: BlocHookOptions<InstanceType<B>>,\n): HookTypes<B> {\n // Determine ID for isolated or shared blocs\n // const base = bloc as unknown as BlocBaseAbstract;\n // const isIsolated = base.isolated;\n // const effectiveBlocId = isIsolated ? rid : blocId;\n const {\n externalStore,\n usedKeys,\n usedClassPropKeys,\n instance,\n rid,\n } = useExternalBlocStore(bloc, options);\n\n // Subscribe to state changes using React's external store API\n const state = useSyncExternalStore<BlocState<InstanceType<B>>>(\n externalStore.subscribe,\n externalStore.getSnapshot,\n externalStore.getServerSnapshot,\n );\n\n // Cache proxies to avoid recreation on every render\n const stateProxyCache = useRef<WeakMap<object, object>>(new WeakMap());\n const classProxyCache = useRef<WeakMap<object, object>>(new WeakMap());\n\n const returnState = useMemo(() => {\n if (typeof state !== 'object' || state === null) {\n return state;\n }\n\n // Check cache first\n let proxy = stateProxyCache.current.get(state);\n if (!proxy) {\n proxy = new Proxy(state, {\n get(target, prop) {\n usedKeys.current.add(prop as string);\n const value = target[prop as keyof typeof target];\n return value;\n },\n // Handle symbols and non-enumerable properties\n has(target, prop) {\n return prop in target;\n },\n ownKeys(target) {\n return Reflect.ownKeys(target);\n },\n getOwnPropertyDescriptor(target, prop) {\n return Reflect.getOwnPropertyDescriptor(target, prop);\n }\n });\n stateProxyCache.current.set(state as object, proxy as object);\n }\n return proxy;\n }, [state]);\n\n const returnClass = useMemo(() => {\n if (!instance.current) {\n return null;\n }\n\n // Check cache first\n let proxy = classProxyCache.current.get(instance.current);\n if (!proxy) {\n proxy = new Proxy(instance.current, {\n get(target, prop) {\n if (!target) {\n return null;\n }\n const value = target[prop as keyof InstanceType<B>];\n if (typeof value !== 'function') {\n usedClassPropKeys.current.add(prop as string);\n }\n return value;\n },\n });\n classProxyCache.current.set(instance.current, proxy);\n }\n return proxy;\n }, [instance.current?.uid]);\n\n // Set up bloc lifecycle management\n useEffect(() => {\n const currentInstance = instance.current;\n if (!currentInstance) return;\n \n currentInstance._addConsumer(rid);\n\n // Call onMount callback if provided\n options?.onMount?.(currentInstance);\n\n // Cleanup: remove this component as a consumer using the captured instance\n return () => {\n if (!currentInstance) {\n return;\n }\n options?.onUnmount?.(currentInstance);\n currentInstance._removeConsumer(rid);\n };\n }, [instance.current?.uid, rid]); // Use UID to ensure we re-run when instance changes\n\n return [returnState as BlocState<InstanceType<B>>, returnClass as InstanceType<B>];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","currentInstance","isResetting","observer","e","unSub","_a","useBloc","externalStore","useSyncExternalStore","stateProxyCache","classProxyCache","returnState","proxy","target","prop","returnClass","useEffect","_b"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -26,13 +26,6 @@ declare interface ExternalBlacStore<B extends BlocConstructor<BlocBase<any>>> {
26
26
  /**
27
27
  * Creates an external store that wraps a Bloc instance, providing a React-compatible interface
28
28
  * for subscribing to and accessing bloc state.
29
- *
30
- * @template B - The type of the Bloc instance
31
- * @template S - The type of the Bloc state
32
- * @param bloc - The Bloc instance to wrap
33
- * @param dependencyArray - Function that returns an array of dependencies for the subscription
34
- * @param rid - Unique identifier for the subscription
35
- * @returns An ExternalStore instance that provides methods to subscribe to and access bloc state
36
29
  */
37
30
  export declare const externalBlocStore: <B extends BlocConstructor<BlocBase<any>>>(bloc: B, options: BlocHookOptions<InstanceType<B>> | undefined) => ExternalBlacStore<B>;
38
31
 
package/dist/index.es.js CHANGED
@@ -1,114 +1,148 @@
1
- import { Blac as x } from "@blac/core";
2
- import { useMemo as y, useRef as C, useCallback as E, useSyncExternalStore as _, useEffect as K } from "react";
3
- const P = (l, t) => {
4
- const { id: f, props: S, selector: b } = t ?? {}, r = y(() => crypto.randomUUID(), []), h = l.isolated ? r : f, d = C(/* @__PURE__ */ new Set()), s = C(/* @__PURE__ */ new Set()), o = E(() => x.getBloc(l, {
5
- id: h,
6
- props: S,
7
- instanceRef: r
8
- }), [l, S]), e = C(o()), a = y(
9
- () => (n) => {
10
- const c = e.current;
11
- if (!c)
1
+ import { useMemo as g, useRef as h, useCallback as P, useSyncExternalStore as p, useEffect as K } from "react";
2
+ import { Blac as w } from "@blac/core";
3
+ const k = (d, c) => {
4
+ var t;
5
+ const { id: y, props: x, selector: v } = c ?? {}, s = g(() => crypto.randomUUID(), []), B = d.isolated ? s : y, l = h(/* @__PURE__ */ new Set()), S = h(/* @__PURE__ */ new Set()), I = P(() => w.getBloc(d, {
6
+ id: B,
7
+ props: x,
8
+ instanceRef: s
9
+ }), [d, x]), u = h(I()), b = g(
10
+ () => (e) => {
11
+ const n = u.current;
12
+ if (!n)
12
13
  return [];
13
- if (b)
14
- return b(n);
15
- if (c.defaultDependencySelector)
16
- return c.defaultDependencySelector(n);
17
- if (typeof n != "object")
18
- return [[n]];
19
- const m = [];
20
- for (const i of d.current)
21
- i in n && m.push(n[i]);
22
- const B = [];
23
- for (const i of s.current)
24
- if (i in c)
14
+ if (v)
15
+ return v(e);
16
+ if (n.defaultDependencySelector)
17
+ return n.defaultDependencySelector(e);
18
+ if (typeof e != "object")
19
+ return [[e]];
20
+ const f = [];
21
+ for (const a of l.current)
22
+ a in e && f.push(e[a]);
23
+ const C = [];
24
+ for (const a of S.current)
25
+ if (a in n)
25
26
  try {
26
- const g = c[i];
27
- switch (typeof g) {
27
+ const i = n[a];
28
+ switch (typeof i) {
28
29
  case "function":
29
30
  continue;
30
31
  default:
31
- B.push(g);
32
+ C.push(i);
32
33
  continue;
33
34
  }
34
- } catch (g) {
35
- x.instance.log("useBloc Error", g);
35
+ } catch (i) {
36
+ w.instance.log("useBloc Error", i);
36
37
  }
37
- return [m, B];
38
+ return [f, C];
38
39
  },
39
40
  []
40
- ), I = y(() => ({
41
- subscribe: (n) => {
42
- const c = {
41
+ ), r = g(() => ({
42
+ subscribe: (e) => {
43
+ const n = u.current;
44
+ if (!n)
45
+ return () => {
46
+ };
47
+ let f = !1;
48
+ const C = {
43
49
  fn: () => {
44
50
  try {
45
- d.current = /* @__PURE__ */ new Set(), s.current = /* @__PURE__ */ new Set(), n(e.current.state);
46
- } catch (B) {
51
+ f || (f = !0, l.current = /* @__PURE__ */ new Set(), S.current = /* @__PURE__ */ new Set(), f = !1), e(n.state);
52
+ } catch (i) {
47
53
  console.error({
48
- e: B,
49
- blocInstance: e,
50
- dependencyArray: a
54
+ e: i,
55
+ blocInstance: n,
56
+ dependencyArray: b
51
57
  });
52
58
  }
53
59
  },
54
60
  // Pass the dependency array to control when the subscription is updated
55
- dependencyArray: a,
61
+ dependencyArray: b,
56
62
  // Use the provided id to identify this subscription
57
- id: r
63
+ id: s
58
64
  };
59
- x.activateBloc(e.current);
60
- const m = e.current._observer.subscribe(c);
65
+ w.activateBloc(n);
66
+ const a = n._observer.subscribe(C);
61
67
  return () => {
62
- m();
68
+ a();
63
69
  };
64
70
  },
65
71
  // Return an immutable snapshot of the current bloc state
66
- getSnapshot: () => e.current.state,
72
+ getSnapshot: () => {
73
+ var e;
74
+ return ((e = u.current) == null ? void 0 : e.state) || {};
75
+ },
67
76
  // Server snapshot mirrors the client snapshot in this implementation
68
- getServerSnapshot: () => e.current.state
69
- }), []);
77
+ getServerSnapshot: () => {
78
+ var e;
79
+ return ((e = u.current) == null ? void 0 : e.state) || {};
80
+ }
81
+ }), [(t = u.current) == null ? void 0 : t.uid]);
70
82
  return {
71
- usedKeys: d,
72
- usedClassPropKeys: s,
73
- externalStore: I,
74
- instance: e,
75
- rid: r
83
+ usedKeys: l,
84
+ usedClassPropKeys: S,
85
+ externalStore: r,
86
+ instance: u,
87
+ rid: s
76
88
  };
77
- }, k = P;
78
- function j(l, t) {
79
- var s;
89
+ };
90
+ function E(d, c) {
91
+ var u, b;
80
92
  const {
81
- externalStore: f,
82
- usedKeys: S,
83
- usedClassPropKeys: b,
84
- instance: r,
85
- rid: v
86
- } = k(l, t), u = _(
87
- f.subscribe,
88
- f.getSnapshot,
89
- f.getServerSnapshot
90
- ), h = y(() => typeof u == "object" ? new Proxy(u, {
91
- get(o, e) {
92
- return S.current.add(e), u[e];
93
- }
94
- }) : u, [u]), d = y(() => new Proxy(r.current, {
95
- get(o, e) {
96
- if (!r.current)
97
- return null;
98
- const a = r.current[e];
99
- return typeof a != "function" && b.current.add(e), a;
100
- }
101
- }), [(s = r.current) == null ? void 0 : s.uid]);
93
+ externalStore: y,
94
+ usedKeys: x,
95
+ usedClassPropKeys: v,
96
+ instance: s,
97
+ rid: m
98
+ } = k(d, c), o = p(
99
+ y.subscribe,
100
+ y.getSnapshot,
101
+ y.getServerSnapshot
102
+ ), B = h(/* @__PURE__ */ new WeakMap()), l = h(/* @__PURE__ */ new WeakMap()), S = g(() => {
103
+ if (typeof o != "object" || o === null)
104
+ return o;
105
+ let r = B.current.get(o);
106
+ return r || (r = new Proxy(o, {
107
+ get(t, e) {
108
+ return x.current.add(e), t[e];
109
+ },
110
+ // Handle symbols and non-enumerable properties
111
+ has(t, e) {
112
+ return e in t;
113
+ },
114
+ ownKeys(t) {
115
+ return Reflect.ownKeys(t);
116
+ },
117
+ getOwnPropertyDescriptor(t, e) {
118
+ return Reflect.getOwnPropertyDescriptor(t, e);
119
+ }
120
+ }), B.current.set(o, r)), r;
121
+ }, [o]), I = g(() => {
122
+ if (!s.current)
123
+ return null;
124
+ let r = l.current.get(s.current);
125
+ return r || (r = new Proxy(s.current, {
126
+ get(t, e) {
127
+ if (!t)
128
+ return null;
129
+ const n = t[e];
130
+ return typeof n != "function" && v.current.add(e), n;
131
+ }
132
+ }), l.current.set(s.current, r)), r;
133
+ }, [(u = s.current) == null ? void 0 : u.uid]);
102
134
  return K(() => {
103
- var o;
104
- return r.current._addConsumer(v), (o = t == null ? void 0 : t.onMount) == null || o.call(t, r.current), () => {
105
- var e;
106
- r.current && ((e = t == null ? void 0 : t.onUnmount) == null || e.call(t, r.current), r.current._removeConsumer(v));
107
- };
108
- }, [r.current, v]), [h, d];
135
+ var t;
136
+ const r = s.current;
137
+ if (r)
138
+ return r._addConsumer(m), (t = c == null ? void 0 : c.onMount) == null || t.call(c, r), () => {
139
+ var e;
140
+ r && ((e = c == null ? void 0 : c.onUnmount) == null || e.call(c, r), r._removeConsumer(m));
141
+ };
142
+ }, [(b = s.current) == null ? void 0 : b.uid, m]), [S, I];
109
143
  }
110
144
  export {
111
145
  k as externalBlocStore,
112
- j as useBloc
146
+ E as useBloc
113
147
  };
114
148
  //# sourceMappingURL=index.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","sources":["../src/externalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState } from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\nimport { BlocHookOptions } from './useBloc';\n\nexport interface ExternalStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n /**\n * Subscribes to changes in the store and returns an unsubscribe function.\n * @param onStoreChange - Callback function that will be called whenever the store changes\n * @returns A function that can be called to unsubscribe from store changes\n */\n subscribe: (onStoreChange: (state: BlocState<InstanceType<B>>) => void) => () => void;\n\n /**\n * Gets the current snapshot of the store state.\n * @returns The current state of the store\n */\n getSnapshot: () => BlocState<InstanceType<B>>;\n\n /**\n * Gets the server snapshot of the store state.\n * This is optional and defaults to the same value as getSnapshot.\n * @returns The server state of the store\n */\n getServerSnapshot?: () => BlocState<InstanceType<B>>;\n}\n\nexport interface ExternalBlacStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n usedKeys: React.RefObject<Set<string>>;\n usedClassPropKeys: React.RefObject<Set<string>>;\n externalStore: ExternalStore<B>;\n instance: React.RefObject<InstanceType<B>>;\n rid: string;\n}\n\n/**\n * Creates an external store that wraps a Bloc instance, providing a React-compatible interface\n * for subscribing to and accessing bloc state.\n * \n * @template B - The type of the Bloc instance\n * @template S - The type of the Bloc state\n * @param bloc - The Bloc instance to wrap\n * @param dependencyArray - Function that returns an array of dependencies for the subscription\n * @param rid - Unique identifier for the subscription\n * @returns An ExternalStore instance that provides methods to subscribe to and access bloc state\n */\nconst useExternalBlocStore = <\n B extends BlocConstructor<BlocBase<any>>\n>(\n bloc: B,\n options: BlocHookOptions<InstanceType<B>> | undefined,\n): ExternalBlacStore<B> => {\n const { id: blocId, props, selector } = options ?? {};\n\n const rid = useMemo(() => {\n return crypto.randomUUID();\n }, []);\n\n const base = bloc as unknown as BlocBaseAbstract;\n\n const isIsolated = base.isolated;\n const effectiveBlocId = isIsolated ? rid : blocId;\n\n const usedKeys = useRef<Set<string>>(new Set());\n const usedClassPropKeys = useRef<Set<string>>(new Set());\n\n const getBloc = useCallback(() => {\n return Blac.getBloc(bloc, {\n id: effectiveBlocId,\n props,\n instanceRef: rid\n });\n }, [bloc, props]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n\n const dependencyArray: BlocHookDependencyArrayFn<BlocState<InstanceType<B>>> =\n useMemo(\n () =>\n (newState): ReturnType<BlocHookDependencyArrayFn<BlocState<InstanceType<B>>>> => {\n const instance = blocInstance.current;\n\n if (!instance) {\n return [];\n }\n\n // Use custom dependency selector if provided\n if (selector) {\n return selector(newState);\n }\n\n // Fall back to bloc's default dependency selector if available\n if (instance.defaultDependencySelector) {\n return instance.defaultDependencySelector(newState);\n }\n\n // For primitive states, use default selector\n if (typeof newState !== 'object') {\n // Default behavior for primitive states: re-render if the state itself changes.\n return [[newState]];\n }\n\n // For object states, track which properties were actually used\n const usedStateValues: string[] = [];\n for (const key of usedKeys.current) {\n if (key in newState) {\n usedStateValues.push(newState[key as keyof typeof newState]);\n }\n }\n\n // Track used class properties for dependency tracking, this enables rerenders when class getters change\n const usedClassValues: unknown[] = [];\n for (const key of usedClassPropKeys.current) {\n if (key in instance) {\n try {\n const value = instance[key as keyof InstanceType<B>];\n switch (typeof value) {\n case 'function':\n continue;\n default:\n usedClassValues.push(value);\n continue;\n }\n } catch (error) {\n Blac.instance.log('useBloc Error', error);\n }\n }\n }\n\n return [usedStateValues, usedClassValues];\n },\n [],\n );\n\n const state: ExternalStore<B> = useMemo(() => {\n return {\n subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {\n const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n\n listener(blocInstance.current.state);\n } catch (e) {\n // Log any errors that occur during the listener callback\n // This ensures errors in listeners don't break the entire application\n console.error({\n e,\n blocInstance,\n dependencyArray,\n });\n }\n },\n // Pass the dependency array to control when the subscription is updated\n dependencyArray,\n // Use the provided id to identify this subscription\n id: rid,\n }\n\n Blac.activateBloc(blocInstance.current);\n\n // Subscribe to the bloc's observer with the provided listener function\n // This will trigger the callback whenever the bloc's state changes\n const unSub = blocInstance.current._observer.subscribe(observer);\n\n // Return an unsubscribe function that can be called to clean up the subscription\n return () => {\n unSub();\n };\n },\n // Return an immutable snapshot of the current bloc state\n getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current.state,\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current.state,\n }\n }, []);\n\n return {\n usedKeys,\n usedClassPropKeys,\n externalStore: state,\n instance: blocInstance,\n rid,\n };\n};\n\nexport default useExternalBlocStore;\n","import {\n BlocBase,\n BlocConstructor,\n BlocHookDependencyArrayFn,\n BlocState,\n InferPropsFromGeneric,\n} from '@blac/core';\nimport {\n useEffect,\n useMemo,\n useSyncExternalStore\n} from 'react';\nimport useExternalBlocStore from './externalBlocStore';\n\n/**\n * Type definition for the return type of the useBloc hook\n * @template B - Bloc constructor type\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * Configuration options for the useBloc hook\n */\nexport interface BlocHookOptions<B extends BlocBase<any>> {\n id?: string;\n selector?: BlocHookDependencyArrayFn<BlocState<B>>;\n props?: InferPropsFromGeneric<B>;\n onMount?: (bloc: B) => void;\n onUnmount?: (bloc: B) => void;\n}\n\n/**\n * Default dependency selector that wraps the entire state in an array\n * @template T - State type\n * @param {T} s - Current state\n * @returns {Array<Array<T>>} Dependency array containing the entire state\n */\n\nconst log = (...args: unknown[]) => {\n console.log('useBloc', ...args);\n};\n\n/**\n * React hook for integrating with Blac state management\n *\n * This hook connects a React component to a Bloc state container, providing\n * automatic re-rendering when relevant state changes, dependency tracking,\n * and proper lifecycle management.\n *\n * @template B - Bloc constructor type\n * @template O - BlocHookOptions type\n * @param {B} bloc - Bloc constructor class\n * @param {O} [options] - Configuration options for the hook\n * @returns {HookTypes<B>} Tuple containing [state, bloc instance]\n *\n * @example\n * const [state, counterBloc] = useBloc(CounterBloc);\n * // Access state\n * console.log(state.count);\n * // Call bloc methods\n * counterBloc.increment();\n */\nexport default function useBloc<B extends BlocConstructor<BlocBase<any>>>(\n bloc: B,\n options?: BlocHookOptions<InstanceType<B>>,\n): HookTypes<B> {\n // Determine ID for isolated or shared blocs\n // const base = bloc as unknown as BlocBaseAbstract;\n // const isIsolated = base.isolated;\n // const effectiveBlocId = isIsolated ? rid : blocId;\n const {\n externalStore,\n usedKeys,\n usedClassPropKeys,\n instance,\n rid,\n } = useExternalBlocStore(bloc, options);\n\n // Subscribe to state changes using React's external store API\n const state = useSyncExternalStore<BlocState<InstanceType<B>>>(\n externalStore.subscribe,\n externalStore.getSnapshot,\n externalStore.getServerSnapshot,\n );\n\n const returnState = useMemo(() => {\n return typeof state === 'object'\n ? new Proxy(state, {\n get(_, prop) {\n usedKeys.current.add(prop as string);\n const value = state[prop as keyof typeof state];\n return value;\n },\n })\n : state;\n }, [state]);\n\n const returnClass = useMemo(() => {\n return new Proxy(instance.current, {\n get(_, prop) {\n if (!instance.current) {\n return null;\n }\n const value = instance.current[prop as keyof InstanceType<B>];\n if (typeof value !== 'function') {\n usedClassPropKeys.current.add(prop as string);\n }\n return value;\n },\n });\n }, [instance.current?.uid]);\n\n // Set up bloc lifecycle management\n useEffect(() => {\n instance.current._addConsumer(rid);\n\n // Call onMount callback if provided\n options?.onMount?.(instance.current);\n\n // Cleanup: remove this component as a consumer using the captured instance\n return () => {\n if (!instance.current) {\n return;\n }\n options?.onUnmount?.(instance.current);\n instance.current._removeConsumer(rid);\n };\n }, [instance.current, rid]); // Do not add options.onMount to deps, it will cause a loop\n\n return [returnState, returnClass];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","observer","e","unSub","useExternalBlocStore$1","useBloc","externalStore","useSyncExternalStore","returnState","_","prop","returnClass","_a","useEffect"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","sources":["../src/useExternalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState } from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\nimport { BlocHookOptions } from './useBloc';\n\nexport interface ExternalStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n /**\n * Subscribes to changes in the store and returns an unsubscribe function.\n * @param onStoreChange - Callback function that will be called whenever the store changes\n * @returns A function that can be called to unsubscribe from store changes\n */\n subscribe: (onStoreChange: (state: BlocState<InstanceType<B>>) => void) => () => void;\n\n /**\n * Gets the current snapshot of the store state.\n * @returns The current state of the store\n */\n getSnapshot: () => BlocState<InstanceType<B>>;\n\n /**\n * Gets the server snapshot of the store state.\n * This is optional and defaults to the same value as getSnapshot.\n * @returns The server state of the store\n */\n getServerSnapshot?: () => BlocState<InstanceType<B>>;\n}\n\nexport interface ExternalBlacStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n usedKeys: React.RefObject<Set<string>>;\n usedClassPropKeys: React.RefObject<Set<string>>;\n externalStore: ExternalStore<B>;\n instance: React.RefObject<InstanceType<B>>;\n rid: string;\n}\n\n/**\n * Creates an external store that wraps a Bloc instance, providing a React-compatible interface\n * for subscribing to and accessing bloc state.\n */\nconst useExternalBlocStore = <\n B extends BlocConstructor<BlocBase<any>>\n>(\n bloc: B,\n options: BlocHookOptions<InstanceType<B>> | undefined,\n): ExternalBlacStore<B> => {\n const { id: blocId, props, selector } = options ?? {};\n\n const rid = useMemo(() => {\n return crypto.randomUUID();\n }, []);\n\n const base = bloc as unknown as BlocBaseAbstract;\n\n const isIsolated = base.isolated;\n const effectiveBlocId = isIsolated ? rid : blocId;\n\n const usedKeys = useRef<Set<string>>(new Set());\n const usedClassPropKeys = useRef<Set<string>>(new Set());\n\n const getBloc = useCallback(() => {\n return Blac.getBloc(bloc, {\n id: effectiveBlocId,\n props,\n instanceRef: rid\n });\n }, [bloc, props]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n\n const dependencyArray: BlocHookDependencyArrayFn<BlocState<InstanceType<B>>> =\n useMemo(\n () =>\n (newState): ReturnType<BlocHookDependencyArrayFn<BlocState<InstanceType<B>>>> => {\n const instance = blocInstance.current;\n\n if (!instance) {\n return [];\n }\n\n // Use custom dependency selector if provided\n if (selector) {\n return selector(newState);\n }\n\n // Fall back to bloc's default dependency selector if available\n if (instance.defaultDependencySelector) {\n return instance.defaultDependencySelector(newState);\n }\n\n // For primitive states, use default selector\n if (typeof newState !== 'object') {\n // Default behavior for primitive states: re-render if the state itself changes.\n return [[newState]];\n }\n\n // For object states, track which properties were actually used\n const usedStateValues: string[] = [];\n for (const key of usedKeys.current) {\n if (key in newState) {\n usedStateValues.push(newState[key as keyof typeof newState]);\n }\n }\n\n // Track used class properties for dependency tracking, this enables rerenders when class getters change\n const usedClassValues: unknown[] = [];\n for (const key of usedClassPropKeys.current) {\n if (key in instance) {\n try {\n const value = instance[key as keyof InstanceType<B>];\n switch (typeof value) {\n case 'function':\n continue;\n default:\n usedClassValues.push(value);\n continue;\n }\n } catch (error) {\n Blac.instance.log('useBloc Error', error);\n }\n }\n }\n\n return [usedStateValues, usedClassValues];\n },\n [],\n );\n\n const state: ExternalStore<B> = useMemo(() => {\n return {\n subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {\n const currentInstance = blocInstance.current;\n if (!currentInstance) {\n return () => {}; // Return no-op if no instance\n }\n\n // Use a flag to prevent multiple resets during the same listener execution\n let isResetting = false;\n\n const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n if (!isResetting) {\n isResetting = true;\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n isResetting = false;\n }\n\n listener(currentInstance.state);\n } catch (e) {\n // Log any errors that occur during the listener callback\n // This ensures errors in listeners don't break the entire application\n console.error({\n e,\n blocInstance: currentInstance,\n dependencyArray,\n });\n }\n },\n // Pass the dependency array to control when the subscription is updated\n dependencyArray,\n // Use the provided id to identify this subscription\n id: rid,\n }\n\n Blac.activateBloc(currentInstance);\n\n // Subscribe to the bloc's observer with the provided listener function\n // This will trigger the callback whenever the bloc's state changes\n const unSub = currentInstance._observer.subscribe(observer);\n\n // Return an unsubscribe function that can be called to clean up the subscription\n return () => {\n unSub();\n };\n },\n // Return an immutable snapshot of the current bloc state\n getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),\n }\n }, [blocInstance.current?.uid]); // Re-create store when instance changes\n\n return {\n usedKeys,\n usedClassPropKeys,\n externalStore: state,\n instance: blocInstance,\n rid,\n };\n};\n\nexport default useExternalBlocStore;\n","import {\n BlocBase,\n BlocConstructor,\n BlocHookDependencyArrayFn,\n BlocState,\n InferPropsFromGeneric,\n} from '@blac/core';\nimport {\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore\n} from 'react';\nimport useExternalBlocStore from './useExternalBlocStore';\n\n/**\n * Type definition for the return type of the useBloc hook\n * @template B - Bloc constructor type\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * Configuration options for the useBloc hook\n */\nexport interface BlocHookOptions<B extends BlocBase<any>> {\n id?: string;\n selector?: BlocHookDependencyArrayFn<BlocState<B>>;\n props?: InferPropsFromGeneric<B>;\n onMount?: (bloc: B) => void;\n onUnmount?: (bloc: B) => void;\n}\n\n/**\n * Default dependency selector that wraps the entire state in an array\n * @template T - State type\n * @param {T} s - Current state\n * @returns {Array<Array<T>>} Dependency array containing the entire state\n */\n\nconst log = (...args: unknown[]) => {\n console.log('useBloc', ...args);\n};\n\n/**\n * React hook for integrating with Blac state management\n *\n * This hook connects a React component to a Bloc state container, providing\n * automatic re-rendering when relevant state changes, dependency tracking,\n * and proper lifecycle management.\n *\n * @template B - Bloc constructor type\n * @template O - BlocHookOptions type\n * @param {B} bloc - Bloc constructor class\n * @param {O} [options] - Configuration options for the hook\n * @returns {HookTypes<B>} Tuple containing [state, bloc instance]\n *\n * @example\n * const [state, counterBloc] = useBloc(CounterBloc);\n * // Access state\n * console.log(state.count);\n * // Call bloc methods\n * counterBloc.increment();\n */\nexport default function useBloc<B extends BlocConstructor<BlocBase<any>>>(\n bloc: B,\n options?: BlocHookOptions<InstanceType<B>>,\n): HookTypes<B> {\n // Determine ID for isolated or shared blocs\n // const base = bloc as unknown as BlocBaseAbstract;\n // const isIsolated = base.isolated;\n // const effectiveBlocId = isIsolated ? rid : blocId;\n const {\n externalStore,\n usedKeys,\n usedClassPropKeys,\n instance,\n rid,\n } = useExternalBlocStore(bloc, options);\n\n // Subscribe to state changes using React's external store API\n const state = useSyncExternalStore<BlocState<InstanceType<B>>>(\n externalStore.subscribe,\n externalStore.getSnapshot,\n externalStore.getServerSnapshot,\n );\n\n // Cache proxies to avoid recreation on every render\n const stateProxyCache = useRef<WeakMap<object, object>>(new WeakMap());\n const classProxyCache = useRef<WeakMap<object, object>>(new WeakMap());\n\n const returnState = useMemo(() => {\n if (typeof state !== 'object' || state === null) {\n return state;\n }\n\n // Check cache first\n let proxy = stateProxyCache.current.get(state);\n if (!proxy) {\n proxy = new Proxy(state, {\n get(target, prop) {\n usedKeys.current.add(prop as string);\n const value = target[prop as keyof typeof target];\n return value;\n },\n // Handle symbols and non-enumerable properties\n has(target, prop) {\n return prop in target;\n },\n ownKeys(target) {\n return Reflect.ownKeys(target);\n },\n getOwnPropertyDescriptor(target, prop) {\n return Reflect.getOwnPropertyDescriptor(target, prop);\n }\n });\n stateProxyCache.current.set(state as object, proxy as object);\n }\n return proxy;\n }, [state]);\n\n const returnClass = useMemo(() => {\n if (!instance.current) {\n return null;\n }\n\n // Check cache first\n let proxy = classProxyCache.current.get(instance.current);\n if (!proxy) {\n proxy = new Proxy(instance.current, {\n get(target, prop) {\n if (!target) {\n return null;\n }\n const value = target[prop as keyof InstanceType<B>];\n if (typeof value !== 'function') {\n usedClassPropKeys.current.add(prop as string);\n }\n return value;\n },\n });\n classProxyCache.current.set(instance.current, proxy);\n }\n return proxy;\n }, [instance.current?.uid]);\n\n // Set up bloc lifecycle management\n useEffect(() => {\n const currentInstance = instance.current;\n if (!currentInstance) return;\n \n currentInstance._addConsumer(rid);\n\n // Call onMount callback if provided\n options?.onMount?.(currentInstance);\n\n // Cleanup: remove this component as a consumer using the captured instance\n return () => {\n if (!currentInstance) {\n return;\n }\n options?.onUnmount?.(currentInstance);\n currentInstance._removeConsumer(rid);\n };\n }, [instance.current?.uid, rid]); // Use UID to ensure we re-run when instance changes\n\n return [returnState as BlocState<InstanceType<B>>, returnClass as InstanceType<B>];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","currentInstance","isResetting","observer","e","unSub","_a","useBloc","externalStore","useSyncExternalStore","stateProxyCache","classProxyCache","returnState","proxy","target","prop","returnClass","useEffect","_b"],"mappings":";;AA0CM,MAAAA,IAAuB,CAG3BC,GACAC,MACyB;;AACzB,QAAM,EAAE,IAAIC,GAAQ,OAAAC,GAAO,UAAAC,EAAS,IAAIH,KAAW,CAAC,GAE9CI,IAAMC,EAAQ,MACX,OAAO,WAAW,GACxB,EAAE,GAKCC,IAHOP,EAEW,WACaK,IAAMH,GAErCM,IAAWC,EAAwB,oBAAA,KAAK,GACxCC,IAAoBD,EAAwB,oBAAA,KAAK,GAEjDE,IAAUC,EAAY,MACnBC,EAAK,QAAQb,GAAM;AAAA,IACxB,IAAIO;AAAA,IACJ,OAAAJ;AAAA,IACA,aAAaE;AAAA,EAAA,CACd,GACA,CAACL,GAAMG,CAAK,CAAC,GAEVW,IAAeL,EAAwBE,GAAS,GAGhDI,IACJT;AAAA,IACE,MACE,CAACU,MAAgF;AAC/E,YAAMC,IAAWH,EAAa;AAE9B,UAAI,CAACG;AACH,eAAO,CAAC;AAIV,UAAIb;AACF,eAAOA,EAASY,CAAQ;AAI1B,UAAIC,EAAS;AACJ,eAAAA,EAAS,0BAA0BD,CAAQ;AAIhD,UAAA,OAAOA,KAAa;AAEf,eAAA,CAAC,CAACA,CAAQ,CAAC;AAIpB,YAAME,IAA4B,CAAC;AACxB,iBAAAC,KAAOX,EAAS;AACzB,QAAIW,KAAOH,KACOE,EAAA,KAAKF,EAASG,CAA4B,CAAC;AAK/D,YAAMC,IAA6B,CAAC;AACzB,iBAAAD,KAAOT,EAAkB;AAClC,YAAIS,KAAOF;AACL,cAAA;AACI,kBAAAI,IAAQJ,EAASE,CAA4B;AACnD,oBAAQ,OAAOE,GAAO;AAAA,cACpB,KAAK;AACH;AAAA,cACF;AACE,gBAAAD,EAAgB,KAAKC,CAAK;AAC1B;AAAA,YAAA;AAAA,mBAEGC,GAAO;AACT,YAAAT,EAAA,SAAS,IAAI,iBAAiBS,CAAK;AAAA,UAAA;AAKvC,aAAA,CAACJ,GAAiBE,CAAe;AAAA,IAC1C;AAAA,IACF,CAAA;AAAA,EACF,GAEIG,IAA0BjB,EAAQ,OAC/B;AAAA,IACL,WAAW,CAACkB,MAA0D;AACpE,YAAMC,IAAkBX,EAAa;AACrC,UAAI,CAACW;AACH,eAAO,MAAM;AAAA,QAAC;AAIhB,UAAIC,IAAc;AAElB,YAAMC,IAAqD;AAAA,QACzD,IAAI,MAAM;AACJ,cAAA;AACF,YAAKD,MACWA,IAAA,IACLlB,EAAA,8BAAc,IAAI,GACTE,EAAA,8BAAc,IAAI,GACtBgB,IAAA,KAGhBF,EAASC,EAAgB,KAAK;AAAA,mBACvBG,GAAG;AAGV,oBAAQ,MAAM;AAAA,cACZ,GAAAA;AAAA,cACA,cAAcH;AAAA,cACd,iBAAAV;AAAA,YAAA,CACD;AAAA,UAAA;AAAA,QAEL;AAAA;AAAA,QAEA,iBAAAA;AAAA;AAAA,QAEA,IAAIV;AAAA,MACN;AAEA,MAAAQ,EAAK,aAAaY,CAAe;AAIjC,YAAMI,IAAQJ,EAAgB,UAAU,UAAUE,CAAQ;AAG1D,aAAO,MAAM;AACL,QAAAE,EAAA;AAAA,MACR;AAAA,IACF;AAAA;AAAA,IAEA,aAAa,MAAkC;;AAAA,eAAAC,IAAAhB,EAAa,YAAb,gBAAAgB,EAAsB,UAAU,CAAC;AAAA;AAAA;AAAA,IAEhF,mBAAmB,MAAkC;;AAAA,eAAAA,IAAAhB,EAAa,YAAb,gBAAAgB,EAAsB,UAAU,CAAA;AAAA;AAAA,EACvF,IACC,EAACA,IAAAhB,EAAa,YAAb,gBAAAgB,EAAsB,GAAG,CAAC;AAEvB,SAAA;AAAA,IACL,UAAAtB;AAAA,IACA,mBAAAE;AAAA,IACA,eAAea;AAAA,IACf,UAAUT;AAAA,IACV,KAAAT;AAAA,EACF;AACF;AChIwB,SAAA0B,EACtB/B,GACAC,GACc;;AAKR,QAAA;AAAA,IACJ,eAAA+B;AAAA,IACA,UAAAxB;AAAA,IACA,mBAAAE;AAAA,IACA,UAAAO;AAAA,IACA,KAAAZ;AAAA,EAAA,IACEN,EAAqBC,GAAMC,CAAO,GAGhCsB,IAAQU;AAAA,IACZD,EAAc;AAAA,IACdA,EAAc;AAAA,IACdA,EAAc;AAAA,EAChB,GAGME,IAAkBzB,EAAoC,oBAAA,SAAS,GAC/D0B,IAAkB1B,EAAoC,oBAAA,SAAS,GAE/D2B,IAAc9B,EAAQ,MAAM;AAChC,QAAI,OAAOiB,KAAU,YAAYA,MAAU;AAClC,aAAAA;AAIT,QAAIc,IAAQH,EAAgB,QAAQ,IAAIX,CAAK;AAC7C,WAAKc,MACKA,IAAA,IAAI,MAAMd,GAAO;AAAA,MACvB,IAAIe,GAAQC,GAAM;AACP,eAAA/B,EAAA,QAAQ,IAAI+B,CAAc,GACrBD,EAAOC,CAA2B;AAAA,MAElD;AAAA;AAAA,MAEA,IAAID,GAAQC,GAAM;AAChB,eAAOA,KAAQD;AAAA,MACjB;AAAA,MACA,QAAQA,GAAQ;AACP,eAAA,QAAQ,QAAQA,CAAM;AAAA,MAC/B;AAAA,MACA,yBAAyBA,GAAQC,GAAM;AAC9B,eAAA,QAAQ,yBAAyBD,GAAQC,CAAI;AAAA,MAAA;AAAA,IACtD,CACD,GACeL,EAAA,QAAQ,IAAIX,GAAiBc,CAAe,IAEvDA;AAAA,EAAA,GACN,CAACd,CAAK,CAAC,GAEJiB,IAAclC,EAAQ,MAAM;AAC5B,QAAA,CAACW,EAAS;AACL,aAAA;AAIT,QAAIoB,IAAQF,EAAgB,QAAQ,IAAIlB,EAAS,OAAO;AACxD,WAAKoB,MACKA,IAAA,IAAI,MAAMpB,EAAS,SAAS;AAAA,MAClC,IAAIqB,GAAQC,GAAM;AAChB,YAAI,CAACD;AACI,iBAAA;AAEH,cAAAjB,IAAQiB,EAAOC,CAA6B;AAC9C,eAAA,OAAOlB,KAAU,cACDX,EAAA,QAAQ,IAAI6B,CAAc,GAEvClB;AAAA,MAAA;AAAA,IACT,CACD,GACDc,EAAgB,QAAQ,IAAIlB,EAAS,SAASoB,CAAK,IAE9CA;AAAA,EACN,GAAA,EAACP,IAAAb,EAAS,YAAT,gBAAAa,EAAkB,GAAG,CAAC;AAG1B,SAAAW,EAAU,MAAM;;AACd,UAAMhB,IAAkBR,EAAS;AACjC,QAAKQ;AAEL,aAAAA,EAAgB,aAAapB,CAAG,IAGhCyB,IAAA7B,KAAA,gBAAAA,EAAS,YAAT,QAAA6B,EAAA,KAAA7B,GAAmBwB,IAGZ,MAAM;;AACX,QAAKA,OAGLK,IAAA7B,KAAA,gBAAAA,EAAS,cAAT,QAAA6B,EAAA,KAAA7B,GAAqBwB,IACrBA,EAAgB,gBAAgBpB,CAAG;AAAA,MACrC;AAAA,KACC,EAACqC,IAAAzB,EAAS,YAAT,gBAAAyB,EAAkB,KAAKrC,CAAG,CAAC,GAExB,CAAC+B,GAA2CI,CAA8B;AACnF;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blac/react",
3
- "version": "2.0.0-rc-3",
3
+ "version": "2.0.0-rc-4",
4
4
  "license": "MIT",
5
5
  "author": "Brendan Mullins <jsnanigans@gmail.com>",
6
6
  "main": "dist/index.cjs.js",
@@ -30,7 +30,7 @@
30
30
  "@types/react-dom": "^18.0.0 || ^19.0.0",
31
31
  "react": "^18.0.0 || ^19.0.0",
32
32
  "react-dom": "^18.0.0 || ^19.0.0",
33
- "@blac/core": "2.0.0-rc-3"
33
+ "@blac/core": "2.0.0-rc-4"
34
34
  },
35
35
  "peerDependenciesMeta": {
36
36
  "@types/react": {
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import useExternalBlocStore from './externalBlocStore';
2
1
  import useBloc from './useBloc';
2
+ import useExternalBlocStore from './useExternalBlocStore';
3
3
 
4
4
  export { useExternalBlocStore as externalBlocStore, useBloc };
package/src/useBloc.tsx CHANGED
@@ -8,9 +8,10 @@ import {
8
8
  import {
9
9
  useEffect,
10
10
  useMemo,
11
+ useRef,
11
12
  useSyncExternalStore
12
13
  } from 'react';
13
- import useExternalBlocStore from './externalBlocStore';
14
+ import useExternalBlocStore from './useExternalBlocStore';
14
15
 
15
16
  /**
16
17
  * Type definition for the return type of the useBloc hook
@@ -86,49 +87,84 @@ export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
86
87
  externalStore.getServerSnapshot,
87
88
  );
88
89
 
90
+ // Cache proxies to avoid recreation on every render
91
+ const stateProxyCache = useRef<WeakMap<object, object>>(new WeakMap());
92
+ const classProxyCache = useRef<WeakMap<object, object>>(new WeakMap());
93
+
89
94
  const returnState = useMemo(() => {
90
- return typeof state === 'object'
91
- ? new Proxy(state, {
92
- get(_, prop) {
93
- usedKeys.current.add(prop as string);
94
- const value = state[prop as keyof typeof state];
95
- return value;
96
- },
97
- })
98
- : state;
95
+ if (typeof state !== 'object' || state === null) {
96
+ return state;
97
+ }
98
+
99
+ // Check cache first
100
+ let proxy = stateProxyCache.current.get(state);
101
+ if (!proxy) {
102
+ proxy = new Proxy(state, {
103
+ get(target, prop) {
104
+ usedKeys.current.add(prop as string);
105
+ const value = target[prop as keyof typeof target];
106
+ return value;
107
+ },
108
+ // Handle symbols and non-enumerable properties
109
+ has(target, prop) {
110
+ return prop in target;
111
+ },
112
+ ownKeys(target) {
113
+ return Reflect.ownKeys(target);
114
+ },
115
+ getOwnPropertyDescriptor(target, prop) {
116
+ return Reflect.getOwnPropertyDescriptor(target, prop);
117
+ }
118
+ });
119
+ stateProxyCache.current.set(state as object, proxy as object);
120
+ }
121
+ return proxy;
99
122
  }, [state]);
100
123
 
101
124
  const returnClass = useMemo(() => {
102
- return new Proxy(instance.current, {
103
- get(_, prop) {
104
- if (!instance.current) {
105
- return null;
106
- }
107
- const value = instance.current[prop as keyof InstanceType<B>];
108
- if (typeof value !== 'function') {
109
- usedClassPropKeys.current.add(prop as string);
110
- }
111
- return value;
112
- },
113
- });
125
+ if (!instance.current) {
126
+ return null;
127
+ }
128
+
129
+ // Check cache first
130
+ let proxy = classProxyCache.current.get(instance.current);
131
+ if (!proxy) {
132
+ proxy = new Proxy(instance.current, {
133
+ get(target, prop) {
134
+ if (!target) {
135
+ return null;
136
+ }
137
+ const value = target[prop as keyof InstanceType<B>];
138
+ if (typeof value !== 'function') {
139
+ usedClassPropKeys.current.add(prop as string);
140
+ }
141
+ return value;
142
+ },
143
+ });
144
+ classProxyCache.current.set(instance.current, proxy);
145
+ }
146
+ return proxy;
114
147
  }, [instance.current?.uid]);
115
148
 
116
149
  // Set up bloc lifecycle management
117
150
  useEffect(() => {
118
- instance.current._addConsumer(rid);
151
+ const currentInstance = instance.current;
152
+ if (!currentInstance) return;
153
+
154
+ currentInstance._addConsumer(rid);
119
155
 
120
156
  // Call onMount callback if provided
121
- options?.onMount?.(instance.current);
157
+ options?.onMount?.(currentInstance);
122
158
 
123
159
  // Cleanup: remove this component as a consumer using the captured instance
124
160
  return () => {
125
- if (!instance.current) {
161
+ if (!currentInstance) {
126
162
  return;
127
163
  }
128
- options?.onUnmount?.(instance.current);
129
- instance.current._removeConsumer(rid);
164
+ options?.onUnmount?.(currentInstance);
165
+ currentInstance._removeConsumer(rid);
130
166
  };
131
- }, [instance.current, rid]); // Do not add options.onMount to deps, it will cause a loop
167
+ }, [instance.current?.uid, rid]); // Use UID to ensure we re-run when instance changes
132
168
 
133
- return [returnState, returnClass];
169
+ return [returnState as BlocState<InstanceType<B>>, returnClass as InstanceType<B>];
134
170
  }
@@ -39,13 +39,6 @@ export interface ExternalBlacStore<
39
39
  /**
40
40
  * Creates an external store that wraps a Bloc instance, providing a React-compatible interface
41
41
  * for subscribing to and accessing bloc state.
42
- *
43
- * @template B - The type of the Bloc instance
44
- * @template S - The type of the Bloc state
45
- * @param bloc - The Bloc instance to wrap
46
- * @param dependencyArray - Function that returns an array of dependencies for the subscription
47
- * @param rid - Unique identifier for the subscription
48
- * @returns An ExternalStore instance that provides methods to subscribe to and access bloc state
49
42
  */
50
43
  const useExternalBlocStore = <
51
44
  B extends BlocConstructor<BlocBase<any>>
@@ -139,19 +132,31 @@ const useExternalBlocStore = <
139
132
  const state: ExternalStore<B> = useMemo(() => {
140
133
  return {
141
134
  subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {
135
+ const currentInstance = blocInstance.current;
136
+ if (!currentInstance) {
137
+ return () => {}; // Return no-op if no instance
138
+ }
139
+
140
+ // Use a flag to prevent multiple resets during the same listener execution
141
+ let isResetting = false;
142
+
142
143
  const observer: BlacObserver<BlocState<InstanceType<B>>> = {
143
144
  fn: () => {
144
145
  try {
145
- usedKeys.current = new Set();
146
- usedClassPropKeys.current = new Set();
146
+ if (!isResetting) {
147
+ isResetting = true;
148
+ usedKeys.current = new Set();
149
+ usedClassPropKeys.current = new Set();
150
+ isResetting = false;
151
+ }
147
152
 
148
- listener(blocInstance.current.state);
153
+ listener(currentInstance.state);
149
154
  } catch (e) {
150
155
  // Log any errors that occur during the listener callback
151
156
  // This ensures errors in listeners don't break the entire application
152
157
  console.error({
153
158
  e,
154
- blocInstance,
159
+ blocInstance: currentInstance,
155
160
  dependencyArray,
156
161
  });
157
162
  }
@@ -162,11 +167,11 @@ const useExternalBlocStore = <
162
167
  id: rid,
163
168
  }
164
169
 
165
- Blac.activateBloc(blocInstance.current);
170
+ Blac.activateBloc(currentInstance);
166
171
 
167
172
  // Subscribe to the bloc's observer with the provided listener function
168
173
  // This will trigger the callback whenever the bloc's state changes
169
- const unSub = blocInstance.current._observer.subscribe(observer);
174
+ const unSub = currentInstance._observer.subscribe(observer);
170
175
 
171
176
  // Return an unsubscribe function that can be called to clean up the subscription
172
177
  return () => {
@@ -174,11 +179,11 @@ const useExternalBlocStore = <
174
179
  };
175
180
  },
176
181
  // Return an immutable snapshot of the current bloc state
177
- getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current.state,
182
+ getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),
178
183
  // Server snapshot mirrors the client snapshot in this implementation
179
- getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current.state,
184
+ getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),
180
185
  }
181
- }, []);
186
+ }, [blocInstance.current?.uid]); // Re-create store when instance changes
182
187
 
183
188
  return {
184
189
  usedKeys,