@blac/react 2.0.0-rc-5 → 2.0.0-rc-6
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 +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.es.js +107 -92
- package/dist/index.es.js.map +1 -1
- package/package.json +2 -2
- package/src/useBloc.tsx +27 -12
- package/src/useExternalBlocStore.ts +35 -17
package/dist/index.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react"),x=require("@blac/core"),M=(l,c)=>{var r;const{id:d,props:m,selector:g}=c??{},s=n.useMemo(()=>x.generateUUID(),[]),S=l.isolated?s:d,i=n.useRef(new Set),y=n.useRef(new Set),b=n.useCallback(()=>x.Blac.getBloc(l,{id:S,props:m,instanceRef:s}),[l,S,m,s]),u=n.useRef(b());n.useMemo(()=>{u.current=b()},[b]);const h=n.useMemo(()=>e=>{const t=u.current;if(!t)return[];if(g)return g(e);if(t.defaultDependencySelector)return t.defaultDependencySelector(e);if(typeof e!="object")return[[e]];const a=[];for(const f of i.current)f in e&&a.push(e[f]);const v=[];for(const f of y.current)if(f in t)try{const w=t[f];switch(typeof w){case"function":continue;default:v.push(w);continue}}catch(w){x.Blac.instance.log("useBloc Error",w)}return i.current.size===0?y.current.size>0?[v]:[[]]:[a,v]},[]),p=n.useMemo(()=>({subscribe:e=>{const t=u.current;if(!t)return()=>{};const a={fn:()=>{try{i.current=new Set,y.current=new Set,e(t.state)}catch(f){console.error({e:f,blocInstance:t,dependencyArray:h})}},dependencyArray:h,id:s};x.Blac.activateBloc(t);const v=t._observer.subscribe(a);return()=>{v()}},getSnapshot:()=>{const e=u.current;return e?e.state:{}},getServerSnapshot:()=>{const e=u.current;return e?e.state:{}}}),[(r=u.current)==null?void 0:r.uid]);return{usedKeys:i,usedClassPropKeys:y,externalStore:p,instance:u,rid:s}};function R(l,c){var h,p;const{externalStore:d,usedKeys:m,usedClassPropKeys:g,instance:s,rid:B}=M(l,c),o=n.useSyncExternalStore(d.subscribe,()=>{const r=d.getSnapshot();if(r===void 0)throw new Error(`[useBloc] State snapshot is undefined for bloc ${l.name}. Bloc may not be properly initialized.`);return r},d.getServerSnapshot?()=>{const r=d.getServerSnapshot();if(r===void 0)throw new Error(`[useBloc] Server state snapshot is undefined for bloc ${l.name}. Bloc may not be properly initialized.`);return r}:void 0),S=n.useRef(new WeakMap),i=n.useRef(new WeakMap),y=n.useMemo(()=>{if(typeof o!="object"||o===null)return o;let r=S.current.get(o);return r||(r=new Proxy(o,{get(e,t){return setTimeout(()=>{m.current.add(t)},0),e[t]},has(e,t){return t in e},ownKeys(e){return Reflect.ownKeys(e)},getOwnPropertyDescriptor(e,t){return Reflect.getOwnPropertyDescriptor(e,t)}}),S.current.set(o,r)),r},[o]),b=n.useMemo(()=>{if(!s.current)return null;let r=i.current.get(s.current);return r||(r=new Proxy(s.current,{get(e,t){if(!e)return null;const a=e[t];return typeof a!="function"&&g.current.add(t),a}}),i.current.set(s.current,r)),r},[(h=s.current)==null?void 0:h.uid]),u=n.useRef({});return n.useEffect(()=>{var e;const r=s.current;if(r)return r._addConsumer(B,u.current),(e=c==null?void 0:c.onMount)==null||e.call(c,r),()=>{var t;r&&((t=c==null?void 0:c.onUnmount)==null||t.call(c,r),r._removeConsumer(B))}},[(p=s.current)==null?void 0:p.uid,B]),[y,b]}exports.externalBlocStore=M;exports.useBloc=R;
|
|
2
2
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/useExternalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState, generateUUID } 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>> | undefined;\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>> | undefined;\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 generateUUID();\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, effectiveBlocId, props, rid]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n // Update the instance when dependencies change\n useMemo(() => {\n blocInstance.current = getBloc();\n }, [getBloc]);\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: unknown[] = [];\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 // If no state properties have been accessed, return empty dependencies to prevent re-renders\n // Class properties can change independently of state\n if (usedKeys.current.size === 0) {\n return usedClassPropKeys.current.size > 0 ? [usedClassValues] : [[]];\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 const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n // Reset dependency tracking before listener is called \n // This ensures we only track properties accessed during the current render\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n\n // Only trigger listener if there are actual subscriptions\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>> | undefined => {\n const instance = blocInstance.current;\n if (!instance) {\n return {} as BlocState<InstanceType<B>>;\n }\n return instance.state;\n },\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> | undefined => {\n const instance = blocInstance.current;\n if (!instance) {\n return {} as BlocState<InstanceType<B>>;\n }\n return instance.state;\n },\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>> | undefined,\n InstanceType<B> | null,\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 * @returns {Array<Array<T>>} Dependency array containing the entire state\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 () => {\n const snapshot = externalStore.getSnapshot();\n if (snapshot === undefined) {\n throw new Error(`[useBloc] State snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`);\n }\n return snapshot;\n },\n externalStore.getServerSnapshot ? () => {\n const serverSnapshot = externalStore.getServerSnapshot!();\n if (serverSnapshot === undefined) {\n throw new Error(`[useBloc] Server state snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`);\n }\n return serverSnapshot;\n } : undefined,\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 // Use setTimeout to defer dependency tracking until after render\n setTimeout(() => {\n usedKeys.current.add(prop as string);\n }, 0);\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 // Create a stable reference object for this component\n const componentRef = useRef({});\n\n // Set up bloc lifecycle management\n useEffect(() => {\n const currentInstance = instance.current;\n if (!currentInstance) return;\n \n // Pass component reference for proper memory management\n currentInstance._addConsumer(rid, componentRef.current);\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 // Safe return with proper typing\n return [returnState, returnClass] as [BlocState<InstanceType<B>>, InstanceType<B>];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","generateUUID","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","currentInstance","observer","e","unSub","_a","useBloc","externalStore","useSyncExternalStore","snapshot","serverSnapshot","stateProxyCache","classProxyCache","returnState","proxy","target","prop","returnClass","componentRef","useEffect","_b"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -40,13 +40,13 @@ declare interface ExternalStore<B extends BlocConstructor<BlocBase<any>>> {
|
|
|
40
40
|
* Gets the current snapshot of the store state.
|
|
41
41
|
* @returns The current state of the store
|
|
42
42
|
*/
|
|
43
|
-
getSnapshot: () => BlocState<InstanceType<B
|
|
43
|
+
getSnapshot: () => BlocState<InstanceType<B>> | undefined;
|
|
44
44
|
/**
|
|
45
45
|
* Gets the server snapshot of the store state.
|
|
46
46
|
* This is optional and defaults to the same value as getSnapshot.
|
|
47
47
|
* @returns The server state of the store
|
|
48
48
|
*/
|
|
49
|
-
getServerSnapshot?: () => BlocState<InstanceType<B
|
|
49
|
+
getServerSnapshot?: () => BlocState<InstanceType<B>> | undefined;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
@@ -54,10 +54,15 @@ declare interface ExternalStore<B extends BlocConstructor<BlocBase<any>>> {
|
|
|
54
54
|
* @template B - Bloc constructor type
|
|
55
55
|
*/
|
|
56
56
|
declare type HookTypes<B extends BlocConstructor<BlocBase<any>>> = [
|
|
57
|
-
BlocState<InstanceType<B
|
|
58
|
-
InstanceType<B>
|
|
57
|
+
BlocState<InstanceType<B>> | undefined,
|
|
58
|
+
InstanceType<B> | null
|
|
59
59
|
];
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Default dependency selector that wraps the entire state in an array
|
|
63
|
+
* @template T - State type
|
|
64
|
+
* @returns {Array<Array<T>>} Dependency array containing the entire state
|
|
65
|
+
*/
|
|
61
66
|
/**
|
|
62
67
|
* React hook for integrating with Blac state management
|
|
63
68
|
*
|
package/dist/index.es.js
CHANGED
|
@@ -1,148 +1,163 @@
|
|
|
1
|
-
import { useMemo as
|
|
2
|
-
import { Blac as
|
|
3
|
-
const k = (
|
|
4
|
-
var
|
|
5
|
-
const { id:
|
|
6
|
-
id:
|
|
7
|
-
props:
|
|
8
|
-
instanceRef:
|
|
9
|
-
}), [
|
|
1
|
+
import { useMemo as y, useRef as S, useCallback as I, useSyncExternalStore as P, useEffect as E } from "react";
|
|
2
|
+
import { generateUUID as K, Blac as C } from "@blac/core";
|
|
3
|
+
const k = (a, n) => {
|
|
4
|
+
var r;
|
|
5
|
+
const { id: f, props: m, selector: g } = n ?? {}, c = y(() => K(), []), h = a.isolated ? c : f, l = S(/* @__PURE__ */ new Set()), d = S(/* @__PURE__ */ new Set()), b = I(() => C.getBloc(a, {
|
|
6
|
+
id: h,
|
|
7
|
+
props: m,
|
|
8
|
+
instanceRef: c
|
|
9
|
+
}), [a, h, m, c]), s = S(b());
|
|
10
|
+
y(() => {
|
|
11
|
+
s.current = b();
|
|
12
|
+
}, [b]);
|
|
13
|
+
const v = y(
|
|
10
14
|
() => (e) => {
|
|
11
|
-
const
|
|
12
|
-
if (!
|
|
15
|
+
const t = s.current;
|
|
16
|
+
if (!t)
|
|
13
17
|
return [];
|
|
14
|
-
if (
|
|
15
|
-
return
|
|
16
|
-
if (
|
|
17
|
-
return
|
|
18
|
+
if (g)
|
|
19
|
+
return g(e);
|
|
20
|
+
if (t.defaultDependencySelector)
|
|
21
|
+
return t.defaultDependencySelector(e);
|
|
18
22
|
if (typeof e != "object")
|
|
19
23
|
return [[e]];
|
|
20
|
-
const
|
|
21
|
-
for (const
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
for (const
|
|
25
|
-
if (
|
|
24
|
+
const o = [];
|
|
25
|
+
for (const i of l.current)
|
|
26
|
+
i in e && o.push(e[i]);
|
|
27
|
+
const p = [];
|
|
28
|
+
for (const i of d.current)
|
|
29
|
+
if (i in t)
|
|
26
30
|
try {
|
|
27
|
-
const
|
|
28
|
-
switch (typeof
|
|
31
|
+
const x = t[i];
|
|
32
|
+
switch (typeof x) {
|
|
29
33
|
case "function":
|
|
30
34
|
continue;
|
|
31
35
|
default:
|
|
32
|
-
|
|
36
|
+
p.push(x);
|
|
33
37
|
continue;
|
|
34
38
|
}
|
|
35
|
-
} catch (
|
|
36
|
-
|
|
39
|
+
} catch (x) {
|
|
40
|
+
C.instance.log("useBloc Error", x);
|
|
37
41
|
}
|
|
38
|
-
return [
|
|
42
|
+
return l.current.size === 0 ? d.current.size > 0 ? [p] : [[]] : [o, p];
|
|
39
43
|
},
|
|
40
44
|
[]
|
|
41
|
-
),
|
|
45
|
+
), w = y(() => ({
|
|
42
46
|
subscribe: (e) => {
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
47
|
+
const t = s.current;
|
|
48
|
+
if (!t)
|
|
45
49
|
return () => {
|
|
46
50
|
};
|
|
47
|
-
|
|
48
|
-
const C = {
|
|
51
|
+
const o = {
|
|
49
52
|
fn: () => {
|
|
50
53
|
try {
|
|
51
|
-
|
|
54
|
+
l.current = /* @__PURE__ */ new Set(), d.current = /* @__PURE__ */ new Set(), e(t.state);
|
|
52
55
|
} catch (i) {
|
|
53
56
|
console.error({
|
|
54
57
|
e: i,
|
|
55
|
-
blocInstance:
|
|
56
|
-
dependencyArray:
|
|
58
|
+
blocInstance: t,
|
|
59
|
+
dependencyArray: v
|
|
57
60
|
});
|
|
58
61
|
}
|
|
59
62
|
},
|
|
60
63
|
// Pass the dependency array to control when the subscription is updated
|
|
61
|
-
dependencyArray:
|
|
64
|
+
dependencyArray: v,
|
|
62
65
|
// Use the provided id to identify this subscription
|
|
63
|
-
id:
|
|
66
|
+
id: c
|
|
64
67
|
};
|
|
65
|
-
|
|
66
|
-
const
|
|
68
|
+
C.activateBloc(t);
|
|
69
|
+
const p = t._observer.subscribe(o);
|
|
67
70
|
return () => {
|
|
68
|
-
|
|
71
|
+
p();
|
|
69
72
|
};
|
|
70
73
|
},
|
|
71
74
|
// Return an immutable snapshot of the current bloc state
|
|
72
75
|
getSnapshot: () => {
|
|
73
|
-
|
|
74
|
-
return
|
|
76
|
+
const e = s.current;
|
|
77
|
+
return e ? e.state : {};
|
|
75
78
|
},
|
|
76
79
|
// Server snapshot mirrors the client snapshot in this implementation
|
|
77
80
|
getServerSnapshot: () => {
|
|
78
|
-
|
|
79
|
-
return
|
|
81
|
+
const e = s.current;
|
|
82
|
+
return e ? e.state : {};
|
|
80
83
|
}
|
|
81
|
-
}), [(
|
|
84
|
+
}), [(r = s.current) == null ? void 0 : r.uid]);
|
|
82
85
|
return {
|
|
83
86
|
usedKeys: l,
|
|
84
|
-
usedClassPropKeys:
|
|
85
|
-
externalStore:
|
|
86
|
-
instance:
|
|
87
|
-
rid:
|
|
87
|
+
usedClassPropKeys: d,
|
|
88
|
+
externalStore: w,
|
|
89
|
+
instance: s,
|
|
90
|
+
rid: c
|
|
88
91
|
};
|
|
89
92
|
};
|
|
90
|
-
function
|
|
91
|
-
var
|
|
93
|
+
function z(a, n) {
|
|
94
|
+
var v, w;
|
|
92
95
|
const {
|
|
93
|
-
externalStore:
|
|
94
|
-
usedKeys:
|
|
95
|
-
usedClassPropKeys:
|
|
96
|
-
instance:
|
|
97
|
-
rid:
|
|
98
|
-
} = k(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
96
|
+
externalStore: f,
|
|
97
|
+
usedKeys: m,
|
|
98
|
+
usedClassPropKeys: g,
|
|
99
|
+
instance: c,
|
|
100
|
+
rid: B
|
|
101
|
+
} = k(a, n), u = P(
|
|
102
|
+
f.subscribe,
|
|
103
|
+
() => {
|
|
104
|
+
const r = f.getSnapshot();
|
|
105
|
+
if (r === void 0)
|
|
106
|
+
throw new Error(`[useBloc] State snapshot is undefined for bloc ${a.name}. Bloc may not be properly initialized.`);
|
|
107
|
+
return r;
|
|
108
|
+
},
|
|
109
|
+
f.getServerSnapshot ? () => {
|
|
110
|
+
const r = f.getServerSnapshot();
|
|
111
|
+
if (r === void 0)
|
|
112
|
+
throw new Error(`[useBloc] Server state snapshot is undefined for bloc ${a.name}. Bloc may not be properly initialized.`);
|
|
113
|
+
return r;
|
|
114
|
+
} : void 0
|
|
115
|
+
), h = S(/* @__PURE__ */ new WeakMap()), l = S(/* @__PURE__ */ new WeakMap()), d = y(() => {
|
|
116
|
+
if (typeof u != "object" || u === null)
|
|
117
|
+
return u;
|
|
118
|
+
let r = h.current.get(u);
|
|
119
|
+
return r || (r = new Proxy(u, {
|
|
120
|
+
get(e, t) {
|
|
121
|
+
return setTimeout(() => {
|
|
122
|
+
m.current.add(t);
|
|
123
|
+
}, 0), e[t];
|
|
109
124
|
},
|
|
110
125
|
// Handle symbols and non-enumerable properties
|
|
111
|
-
has(
|
|
112
|
-
return
|
|
126
|
+
has(e, t) {
|
|
127
|
+
return t in e;
|
|
113
128
|
},
|
|
114
|
-
ownKeys(
|
|
115
|
-
return Reflect.ownKeys(
|
|
129
|
+
ownKeys(e) {
|
|
130
|
+
return Reflect.ownKeys(e);
|
|
116
131
|
},
|
|
117
|
-
getOwnPropertyDescriptor(
|
|
118
|
-
return Reflect.getOwnPropertyDescriptor(
|
|
132
|
+
getOwnPropertyDescriptor(e, t) {
|
|
133
|
+
return Reflect.getOwnPropertyDescriptor(e, t);
|
|
119
134
|
}
|
|
120
|
-
}),
|
|
121
|
-
}, [
|
|
122
|
-
if (!
|
|
135
|
+
}), h.current.set(u, r)), r;
|
|
136
|
+
}, [u]), b = y(() => {
|
|
137
|
+
if (!c.current)
|
|
123
138
|
return null;
|
|
124
|
-
let r = l.current.get(
|
|
125
|
-
return r || (r = new Proxy(
|
|
126
|
-
get(
|
|
127
|
-
if (!
|
|
139
|
+
let r = l.current.get(c.current);
|
|
140
|
+
return r || (r = new Proxy(c.current, {
|
|
141
|
+
get(e, t) {
|
|
142
|
+
if (!e)
|
|
128
143
|
return null;
|
|
129
|
-
const
|
|
130
|
-
return typeof
|
|
144
|
+
const o = e[t];
|
|
145
|
+
return typeof o != "function" && g.current.add(t), o;
|
|
131
146
|
}
|
|
132
|
-
}), l.current.set(
|
|
133
|
-
}, [(
|
|
134
|
-
return
|
|
135
|
-
var
|
|
136
|
-
const r =
|
|
147
|
+
}), l.current.set(c.current, r)), r;
|
|
148
|
+
}, [(v = c.current) == null ? void 0 : v.uid]), s = S({});
|
|
149
|
+
return E(() => {
|
|
150
|
+
var e;
|
|
151
|
+
const r = c.current;
|
|
137
152
|
if (r)
|
|
138
|
-
return r._addConsumer(
|
|
139
|
-
var
|
|
140
|
-
r && ((
|
|
153
|
+
return r._addConsumer(B, s.current), (e = n == null ? void 0 : n.onMount) == null || e.call(n, r), () => {
|
|
154
|
+
var t;
|
|
155
|
+
r && ((t = n == null ? void 0 : n.onUnmount) == null || t.call(n, r), r._removeConsumer(B));
|
|
141
156
|
};
|
|
142
|
-
}, [(
|
|
157
|
+
}, [(w = c.current) == null ? void 0 : w.uid, B]), [d, b];
|
|
143
158
|
}
|
|
144
159
|
export {
|
|
145
160
|
k as externalBlocStore,
|
|
146
|
-
|
|
161
|
+
z as useBloc
|
|
147
162
|
};
|
|
148
163
|
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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":"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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, generateUUID } 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>> | undefined;\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>> | undefined;\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 generateUUID();\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, effectiveBlocId, props, rid]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n // Update the instance when dependencies change\n useMemo(() => {\n blocInstance.current = getBloc();\n }, [getBloc]);\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: unknown[] = [];\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 // If no state properties have been accessed, return empty dependencies to prevent re-renders\n // Class properties can change independently of state\n if (usedKeys.current.size === 0) {\n return usedClassPropKeys.current.size > 0 ? [usedClassValues] : [[]];\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 const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n // Reset dependency tracking before listener is called \n // This ensures we only track properties accessed during the current render\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n\n // Only trigger listener if there are actual subscriptions\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>> | undefined => {\n const instance = blocInstance.current;\n if (!instance) {\n return {} as BlocState<InstanceType<B>>;\n }\n return instance.state;\n },\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> | undefined => {\n const instance = blocInstance.current;\n if (!instance) {\n return {} as BlocState<InstanceType<B>>;\n }\n return instance.state;\n },\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>> | undefined,\n InstanceType<B> | null,\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 * @returns {Array<Array<T>>} Dependency array containing the entire state\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 () => {\n const snapshot = externalStore.getSnapshot();\n if (snapshot === undefined) {\n throw new Error(`[useBloc] State snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`);\n }\n return snapshot;\n },\n externalStore.getServerSnapshot ? () => {\n const serverSnapshot = externalStore.getServerSnapshot!();\n if (serverSnapshot === undefined) {\n throw new Error(`[useBloc] Server state snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`);\n }\n return serverSnapshot;\n } : undefined,\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 // Use setTimeout to defer dependency tracking until after render\n setTimeout(() => {\n usedKeys.current.add(prop as string);\n }, 0);\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 // Create a stable reference object for this component\n const componentRef = useRef({});\n\n // Set up bloc lifecycle management\n useEffect(() => {\n const currentInstance = instance.current;\n if (!currentInstance) return;\n \n // Pass component reference for proper memory management\n currentInstance._addConsumer(rid, componentRef.current);\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 // Safe return with proper typing\n return [returnState, returnClass] as [BlocState<InstanceType<B>>, InstanceType<B>];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","generateUUID","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","currentInstance","observer","e","unSub","_a","useBloc","externalStore","useSyncExternalStore","snapshot","serverSnapshot","stateProxyCache","classProxyCache","returnState","proxy","target","prop","returnClass","componentRef","useEffect","_b"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blac/react",
|
|
3
|
-
"version": "2.0.0-rc-
|
|
3
|
+
"version": "2.0.0-rc-6",
|
|
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-
|
|
33
|
+
"@blac/core": "2.0.0-rc-6"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@types/react": {
|
package/src/useBloc.tsx
CHANGED
|
@@ -18,8 +18,8 @@ import useExternalBlocStore from './useExternalBlocStore';
|
|
|
18
18
|
* @template B - Bloc constructor type
|
|
19
19
|
*/
|
|
20
20
|
type HookTypes<B extends BlocConstructor<BlocBase<any>>> = [
|
|
21
|
-
BlocState<InstanceType<B
|
|
22
|
-
InstanceType<B
|
|
21
|
+
BlocState<InstanceType<B>> | undefined,
|
|
22
|
+
InstanceType<B> | null,
|
|
23
23
|
];
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -36,14 +36,9 @@ export interface BlocHookOptions<B extends BlocBase<any>> {
|
|
|
36
36
|
/**
|
|
37
37
|
* Default dependency selector that wraps the entire state in an array
|
|
38
38
|
* @template T - State type
|
|
39
|
-
* @param {T} s - Current state
|
|
40
39
|
* @returns {Array<Array<T>>} Dependency array containing the entire state
|
|
41
40
|
*/
|
|
42
41
|
|
|
43
|
-
const log = (...args: unknown[]) => {
|
|
44
|
-
console.log('useBloc', ...args);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
42
|
/**
|
|
48
43
|
* React hook for integrating with Blac state management
|
|
49
44
|
*
|
|
@@ -83,8 +78,20 @@ export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
|
|
|
83
78
|
// Subscribe to state changes using React's external store API
|
|
84
79
|
const state = useSyncExternalStore<BlocState<InstanceType<B>>>(
|
|
85
80
|
externalStore.subscribe,
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
() => {
|
|
82
|
+
const snapshot = externalStore.getSnapshot();
|
|
83
|
+
if (snapshot === undefined) {
|
|
84
|
+
throw new Error(`[useBloc] State snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`);
|
|
85
|
+
}
|
|
86
|
+
return snapshot;
|
|
87
|
+
},
|
|
88
|
+
externalStore.getServerSnapshot ? () => {
|
|
89
|
+
const serverSnapshot = externalStore.getServerSnapshot!();
|
|
90
|
+
if (serverSnapshot === undefined) {
|
|
91
|
+
throw new Error(`[useBloc] Server state snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`);
|
|
92
|
+
}
|
|
93
|
+
return serverSnapshot;
|
|
94
|
+
} : undefined,
|
|
88
95
|
);
|
|
89
96
|
|
|
90
97
|
// Cache proxies to avoid recreation on every render
|
|
@@ -101,7 +108,10 @@ export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
|
|
|
101
108
|
if (!proxy) {
|
|
102
109
|
proxy = new Proxy(state, {
|
|
103
110
|
get(target, prop) {
|
|
104
|
-
|
|
111
|
+
// Use setTimeout to defer dependency tracking until after render
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
usedKeys.current.add(prop as string);
|
|
114
|
+
}, 0);
|
|
105
115
|
const value = target[prop as keyof typeof target];
|
|
106
116
|
return value;
|
|
107
117
|
},
|
|
@@ -146,12 +156,16 @@ export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
|
|
|
146
156
|
return proxy;
|
|
147
157
|
}, [instance.current?.uid]);
|
|
148
158
|
|
|
159
|
+
// Create a stable reference object for this component
|
|
160
|
+
const componentRef = useRef({});
|
|
161
|
+
|
|
149
162
|
// Set up bloc lifecycle management
|
|
150
163
|
useEffect(() => {
|
|
151
164
|
const currentInstance = instance.current;
|
|
152
165
|
if (!currentInstance) return;
|
|
153
166
|
|
|
154
|
-
|
|
167
|
+
// Pass component reference for proper memory management
|
|
168
|
+
currentInstance._addConsumer(rid, componentRef.current);
|
|
155
169
|
|
|
156
170
|
// Call onMount callback if provided
|
|
157
171
|
options?.onMount?.(currentInstance);
|
|
@@ -166,5 +180,6 @@ export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
|
|
|
166
180
|
};
|
|
167
181
|
}, [instance.current?.uid, rid]); // Use UID to ensure we re-run when instance changes
|
|
168
182
|
|
|
169
|
-
|
|
183
|
+
// Safe return with proper typing
|
|
184
|
+
return [returnState, returnClass] as [BlocState<InstanceType<B>>, InstanceType<B>];
|
|
170
185
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState } from '@blac/core';
|
|
1
|
+
import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState, generateUUID } from '@blac/core';
|
|
2
2
|
import { useCallback, useMemo, useRef } from 'react';
|
|
3
3
|
import { BlocHookOptions } from './useBloc';
|
|
4
4
|
|
|
@@ -16,14 +16,14 @@ export interface ExternalStore<
|
|
|
16
16
|
* Gets the current snapshot of the store state.
|
|
17
17
|
* @returns The current state of the store
|
|
18
18
|
*/
|
|
19
|
-
getSnapshot: () => BlocState<InstanceType<B
|
|
19
|
+
getSnapshot: () => BlocState<InstanceType<B>> | undefined;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Gets the server snapshot of the store state.
|
|
23
23
|
* This is optional and defaults to the same value as getSnapshot.
|
|
24
24
|
* @returns The server state of the store
|
|
25
25
|
*/
|
|
26
|
-
getServerSnapshot?: () => BlocState<InstanceType<B
|
|
26
|
+
getServerSnapshot?: () => BlocState<InstanceType<B>> | undefined;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface ExternalBlacStore<
|
|
@@ -49,7 +49,7 @@ const useExternalBlocStore = <
|
|
|
49
49
|
const { id: blocId, props, selector } = options ?? {};
|
|
50
50
|
|
|
51
51
|
const rid = useMemo(() => {
|
|
52
|
-
return
|
|
52
|
+
return generateUUID();
|
|
53
53
|
}, []);
|
|
54
54
|
|
|
55
55
|
const base = bloc as unknown as BlocBaseAbstract;
|
|
@@ -66,10 +66,14 @@ const useExternalBlocStore = <
|
|
|
66
66
|
props,
|
|
67
67
|
instanceRef: rid
|
|
68
68
|
});
|
|
69
|
-
}, [bloc, props]);
|
|
69
|
+
}, [bloc, effectiveBlocId, props, rid]);
|
|
70
70
|
|
|
71
71
|
const blocInstance = useRef<InstanceType<B>>(getBloc());
|
|
72
72
|
|
|
73
|
+
// Update the instance when dependencies change
|
|
74
|
+
useMemo(() => {
|
|
75
|
+
blocInstance.current = getBloc();
|
|
76
|
+
}, [getBloc]);
|
|
73
77
|
|
|
74
78
|
const dependencyArray: BlocHookDependencyArrayFn<BlocState<InstanceType<B>>> =
|
|
75
79
|
useMemo(
|
|
@@ -98,7 +102,7 @@ const useExternalBlocStore = <
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
// For object states, track which properties were actually used
|
|
101
|
-
const usedStateValues:
|
|
105
|
+
const usedStateValues: unknown[] = [];
|
|
102
106
|
for (const key of usedKeys.current) {
|
|
103
107
|
if (key in newState) {
|
|
104
108
|
usedStateValues.push(newState[key as keyof typeof newState]);
|
|
@@ -124,6 +128,12 @@ const useExternalBlocStore = <
|
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
|
|
131
|
+
// If no state properties have been accessed, return empty dependencies to prevent re-renders
|
|
132
|
+
// Class properties can change independently of state
|
|
133
|
+
if (usedKeys.current.size === 0) {
|
|
134
|
+
return usedClassPropKeys.current.size > 0 ? [usedClassValues] : [[]];
|
|
135
|
+
}
|
|
136
|
+
|
|
127
137
|
return [usedStateValues, usedClassValues];
|
|
128
138
|
},
|
|
129
139
|
[],
|
|
@@ -137,19 +147,15 @@ const useExternalBlocStore = <
|
|
|
137
147
|
return () => {}; // Return no-op if no instance
|
|
138
148
|
}
|
|
139
149
|
|
|
140
|
-
// Use a flag to prevent multiple resets during the same listener execution
|
|
141
|
-
let isResetting = false;
|
|
142
|
-
|
|
143
150
|
const observer: BlacObserver<BlocState<InstanceType<B>>> = {
|
|
144
151
|
fn: () => {
|
|
145
152
|
try {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
isResetting = false;
|
|
151
|
-
}
|
|
153
|
+
// Reset dependency tracking before listener is called
|
|
154
|
+
// This ensures we only track properties accessed during the current render
|
|
155
|
+
usedKeys.current = new Set();
|
|
156
|
+
usedClassPropKeys.current = new Set();
|
|
152
157
|
|
|
158
|
+
// Only trigger listener if there are actual subscriptions
|
|
153
159
|
listener(currentInstance.state);
|
|
154
160
|
} catch (e) {
|
|
155
161
|
// Log any errors that occur during the listener callback
|
|
@@ -179,9 +185,21 @@ const useExternalBlocStore = <
|
|
|
179
185
|
};
|
|
180
186
|
},
|
|
181
187
|
// Return an immutable snapshot of the current bloc state
|
|
182
|
-
getSnapshot: (): BlocState<InstanceType<B>>
|
|
188
|
+
getSnapshot: (): BlocState<InstanceType<B>> | undefined => {
|
|
189
|
+
const instance = blocInstance.current;
|
|
190
|
+
if (!instance) {
|
|
191
|
+
return {} as BlocState<InstanceType<B>>;
|
|
192
|
+
}
|
|
193
|
+
return instance.state;
|
|
194
|
+
},
|
|
183
195
|
// Server snapshot mirrors the client snapshot in this implementation
|
|
184
|
-
getServerSnapshot: (): BlocState<InstanceType<B>>
|
|
196
|
+
getServerSnapshot: (): BlocState<InstanceType<B>> | undefined => {
|
|
197
|
+
const instance = blocInstance.current;
|
|
198
|
+
if (!instance) {
|
|
199
|
+
return {} as BlocState<InstanceType<B>>;
|
|
200
|
+
}
|
|
201
|
+
return instance.state;
|
|
202
|
+
},
|
|
185
203
|
}
|
|
186
204
|
}, [blocInstance.current?.uid]); // Re-create store when instance changes
|
|
187
205
|
|