@blac/react 2.0.0-rc-10 → 2.0.0-rn-12

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/README.md CHANGED
@@ -123,6 +123,27 @@ The dependency tracking system uses JavaScript Proxies to monitor property acces
123
123
  3. **Intelligent Comparison**: The system separately tracks state dependencies and instance dependencies to handle edge cases where properties are dynamically added/removed
124
124
  4. **Optimized Updates**: Components only re-render when tracked dependencies actually change their values
125
125
 
126
+ #### Configuring Proxy Tracking
127
+
128
+ By default, BlaC uses proxy-based dependency tracking for optimal performance. You can disable this globally if needed:
129
+
130
+ ```tsx
131
+ import { Blac } from '@blac/core';
132
+
133
+ // Disable automatic dependency tracking globally
134
+ Blac.setConfig({ proxyDependencyTracking: false });
135
+
136
+ // Now components will re-render on ANY state change
137
+ // unless you provide manual dependencies
138
+ ```
139
+
140
+ When proxy tracking is disabled:
141
+ - Components re-render on any state change (similar to traditional state management)
142
+ - Manual dependencies via the `selector` option still work as expected
143
+ - Useful for debugging or when proxy behavior causes issues
144
+
145
+ For more configuration options, see the [@blac/core documentation](https://www.npmjs.com/package/@blac/core).
146
+
126
147
  ### Custom Dependency Selector
127
148
 
128
149
  For more control over when your component re-renders, you can provide a custom dependency selector. The selector function receives the current state, previous state, and bloc instance, and should return an array of values to track:
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const S=require("@blac/core"),e=require("react");function I(c,t){const d=e.useRef(0);d.current++;const l=e.useRef({}),r=e.useMemo(()=>new S.BlacAdapter({componentRef:l,blocConstructor:c},t),[]);r.resetConsumerTracking();const s=e.useRef(0);e.useEffect(()=>{s.current++,r.options=t},[t]);const R=e.useRef(0);e.useEffect(()=>(R.current++,r.mount(),()=>{r.unmount()}),[r.blocInstance]);const m=e.useRef(0),n=e.useMemo(()=>(m.current++,o=>{const y=r.createSubscription({onChange:()=>{o()}});return()=>{y()}}),[r.blocInstance]),u=e.useRef(0),a=e.useRef(0),f=e.useSyncExternalStore(n,()=>(u.current++,r.blocInstance.state),()=>{const o=r.blocInstance;return a.current++,o.state}),b=e.useRef(0),i=e.useMemo(()=>(b.current++,r.getProxyState(f)),[f]),g=e.useRef(0),B=e.useMemo(()=>(g.current++,r.getProxyBlocInstance()),[r.blocInstance]);return e.useEffect(()=>{r.updateLastNotified(l.current)}),[i,B]}function x(c,t={}){const d=e.useRef(`external-${S.generateUUID()}`),l=e.useRef(new Set),r=e.useRef(new Set),s=e.useRef(null),R=e.useMemo(()=>{const n=S.Blac.getInstance();if(c.constructor.isolated||c.isolated){const a=new c(t==null?void 0:t.props),f=(t==null?void 0:t.id)||`${c.name}_${S.generateUUID()}`;return a._updateId(f),n.activateBloc(a),a}return n.getBloc(c,{id:t==null?void 0:t.id,props:t==null?void 0:t.props})},[c,t==null?void 0:t.id]),m=e.useMemo(()=>({getSnapshot:()=>{const n=s.current;return n?n.state:void 0},subscribe:n=>{const u=s.current;if(!u)return()=>{};const a=(b,i)=>{try{l.current.clear(),r.current.clear(),t.selector&&u&&t.selector(b,i,u),n.length>0?n(b):n()}catch(g){console.error("Listener error in useExternalBlocStore:",g)}};return u._observer.subscribe({id:d.current,fn:a})},getServerSnapshot:()=>{const n=s.current;return n?n.state:void 0}}),[]);return s.current=R,{externalStore:m,instance:s,usedKeys:l,usedClassPropKeys:r,rid:d.current}}exports.useBloc=I;exports.useExternalBlocStore=x;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const S=require("@blac/core"),r=require("react");function I(n,e){const b=r.useRef(0);b.current++;const l=r.useRef({}),c=r.useMemo(()=>new S.BlacAdapter({componentRef:l,blocConstructor:n},{id:e==null?void 0:e.id,dependencies:e==null?void 0:e.dependencies,props:e==null?void 0:e.props,onMount:e==null?void 0:e.onMount,onUnmount:e==null?void 0:e.onUnmount}),[]);c.resetConsumerTracking();const t=r.useRef(0);r.useEffect(()=>{t.current++,c.options={id:e==null?void 0:e.id,dependencies:e==null?void 0:e.dependencies,props:e==null?void 0:e.props,onMount:e==null?void 0:e.onMount,onUnmount:e==null?void 0:e.onUnmount},(e==null?void 0:e.props)!==void 0&&c.updateProps(e.props)},[e==null?void 0:e.props,e==null?void 0:e.id,e==null?void 0:e.dependencies,e==null?void 0:e.onMount,e==null?void 0:e.onUnmount]);const R=r.useRef(0);r.useEffect(()=>(R.current++,c.mount(),()=>{c.unmount()}),[c.blocInstance]);const B=r.useRef(0),u=r.useMemo(()=>(B.current++,d=>{const y=c.createSubscription({onChange:()=>{d()}});return()=>{y()}}),[c.blocInstance]),a=r.useRef(0),s=r.useRef(0),f=r.useSyncExternalStore(u,()=>(a.current++,c.blocInstance.state),()=>{const d=c.blocInstance;return s.current++,d.state}),m=r.useRef(0),M=r.useMemo(()=>(m.current++,c.getProxyState(f)),[f]),g=r.useRef(0),x=r.useMemo(()=>(g.current++,c.getProxyBlocInstance()),[c.blocInstance]);return r.useEffect(()=>{c.updateLastNotified(l.current)}),[M,x]}function U(n,e={}){const b=r.useRef(`external-${S.generateUUID()}`),l=r.useRef(new Set),c=r.useRef(new Set),t=r.useRef(null),R=r.useMemo(()=>{const u=S.Blac.getInstance();if(n.constructor.isolated||n.isolated){const s=new n(e==null?void 0:e.props),f=(e==null?void 0:e.id)||`${n.name}_${S.generateUUID()}`;return s._updateId(f),u.activateBloc(s),s}return u.getBloc(n,{id:e==null?void 0:e.id,props:e==null?void 0:e.props})},[n,e==null?void 0:e.id]),B=r.useMemo(()=>({getSnapshot:()=>{const u=t.current;return u?u.state:void 0},subscribe:u=>{const a=t.current;if(!a)return()=>{};const s=(m,M)=>{try{l.current.clear(),c.current.clear(),e.selector&&a&&e.selector(m,M,a),u.length>0?u(m):u()}catch(g){console.error("Listener error in useExternalBlocStore:",g)}};return a._observer.subscribe({id:b.current,fn:s})},getServerSnapshot:()=>{const u=t.current;return u?u.state:void 0}}),[]);return t.current=R,{externalStore:B,instance:t,usedKeys:l,usedClassPropKeys:c,rid:b.current}}exports.useBloc=I;exports.useBlocNext=I;exports.useExternalBlocStore=U;
2
2
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n AdapterOptions,\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: AdapterOptions<InstanceType<B>>,\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Track adapter creation\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n options,\n );\n return newAdapter;\n }, []);\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetConsumerTracking();\n\n // Track options changes\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = options;\n }, [options]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter.blocInstance]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter.blocInstance]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n const proxyState = adapter.getProxyState(rawState);\n return proxyState;\n }, [rawState]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getProxyBlocInstance();\n return proxyBloc;\n }, [adapter.blocInstance]);\n\n // Mark consumer as rendered after each render\n useEffect(() => {\n adapter.updateLastNotified(componentRef.current);\n });\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n InferPropsFromGeneric,\n generateUUID,\n} from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n props?: InferPropsFromGeneric<B>;\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(options?.props) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._updateId(uniqueId);\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n props: options?.props,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n const unsubscribe = currentInstance._observer.subscribe({\n id: ridRef.current,\n fn: safeListener,\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","adapter","useMemo","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n AdapterOptions,\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: {\n props?: ConstructorParameters<B>[0];\n id?: string;\n dependencies?: (bloc: InstanceType<B>) => unknown[];\n onMount?: (bloc: InstanceType<B>) => void;\n onUnmount?: (bloc: InstanceType<B>) => void;\n },\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Track adapter creation\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n },\n );\n return newAdapter;\n }, []);\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetConsumerTracking();\n\n // Track options changes and update props\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n };\n\n if (options?.props !== undefined) {\n adapter.updateProps(options.props);\n }\n }, [options?.props, options?.id, options?.dependencies, options?.onMount, options?.onUnmount]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter.blocInstance]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter.blocInstance]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n const proxyState = adapter.getProxyState(rawState);\n return proxyState;\n }, [rawState]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getProxyBlocInstance();\n return proxyBloc;\n }, [adapter.blocInstance]);\n\n // Mark consumer as rendered after each render\n useEffect(() => {\n adapter.updateLastNotified(componentRef.current);\n });\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n InferPropsFromGeneric,\n generateUUID,\n} from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n props?: InferPropsFromGeneric<B>;\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(options?.props) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._updateId(uniqueId);\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n props: options?.props,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n const unsubscribe = currentInstance._observer.subscribe({\n id: ridRef.current,\n fn: safeListener,\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","adapter","useMemo","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { AdapterOptions } from '@blac/core';
2
1
  import { BlocBase } from '@blac/core';
3
2
  import { BlocConstructor } from '@blac/core';
4
3
  import { BlocState } from '@blac/core';
@@ -41,7 +40,15 @@ InstanceType<B>
41
40
  /**
42
41
  * React hook for integrating with Blac state management
43
42
  */
44
- export declare function useBloc<B extends BlocConstructor<BlocBase<any>>>(blocConstructor: B, options?: AdapterOptions<InstanceType<B>>): HookTypes<B>;
43
+ declare function useBloc<B extends BlocConstructor<BlocBase<any>>>(blocConstructor: B, options?: {
44
+ props?: ConstructorParameters<B>[0];
45
+ id?: string;
46
+ dependencies?: (bloc: InstanceType<B>) => unknown[];
47
+ onMount?: (bloc: InstanceType<B>) => void;
48
+ onUnmount?: (bloc: InstanceType<B>) => void;
49
+ }): HookTypes<B>;
50
+ export { useBloc }
51
+ export { useBloc as useBlocNext }
45
52
 
46
53
  /**
47
54
  * Hook for using an external bloc store
package/dist/index.es.js CHANGED
@@ -1,98 +1,111 @@
1
- import { BlacAdapter as C, generateUUID as g, Blac as w } from "@blac/core";
2
- import { useRef as n, useMemo as l, useEffect as B, useSyncExternalStore as v } from "react";
3
- function K(c, e) {
4
- const f = n(0);
5
- f.current++;
6
- const d = n({}), t = l(() => new C(
1
+ import { BlacAdapter as v, generateUUID as h, Blac as w } from "@blac/core";
2
+ import { useRef as u, useMemo as b, useEffect as M, useSyncExternalStore as U } from "react";
3
+ function P(n, e) {
4
+ const m = u(0);
5
+ m.current++;
6
+ const f = u({}), r = b(() => new v(
7
7
  {
8
- componentRef: d,
9
- blocConstructor: c
8
+ componentRef: f,
9
+ blocConstructor: n
10
10
  },
11
- e
11
+ {
12
+ id: e == null ? void 0 : e.id,
13
+ dependencies: e == null ? void 0 : e.dependencies,
14
+ props: e == null ? void 0 : e.props,
15
+ onMount: e == null ? void 0 : e.onMount,
16
+ onUnmount: e == null ? void 0 : e.onUnmount
17
+ }
12
18
  ), []);
13
- t.resetConsumerTracking();
14
- const s = n(0);
15
- B(() => {
16
- s.current++, t.options = e;
17
- }, [e]);
18
- const m = n(0);
19
- B(() => (m.current++, t.mount(), () => {
20
- t.unmount();
21
- }), [t.blocInstance]);
22
- const I = n(0), r = l(() => (I.current++, (o) => {
23
- const y = t.createSubscription({
19
+ r.resetConsumerTracking();
20
+ const t = u(0);
21
+ M(() => {
22
+ t.current++, r.options = {
23
+ id: e == null ? void 0 : e.id,
24
+ dependencies: e == null ? void 0 : e.dependencies,
25
+ props: e == null ? void 0 : e.props,
26
+ onMount: e == null ? void 0 : e.onMount,
27
+ onUnmount: e == null ? void 0 : e.onUnmount
28
+ }, (e == null ? void 0 : e.props) !== void 0 && r.updateProps(e.props);
29
+ }, [e == null ? void 0 : e.props, e == null ? void 0 : e.id, e == null ? void 0 : e.dependencies, e == null ? void 0 : e.onMount, e == null ? void 0 : e.onUnmount]);
30
+ const x = u(0);
31
+ M(() => (x.current++, r.mount(), () => {
32
+ r.unmount();
33
+ }), [r.blocInstance]);
34
+ const B = u(0), c = b(() => (B.current++, (l) => {
35
+ const g = r.createSubscription({
24
36
  onChange: () => {
25
- o();
37
+ l();
26
38
  }
27
39
  });
28
40
  return () => {
29
- y();
41
+ g();
30
42
  };
31
- }), [t.blocInstance]), u = n(0), a = n(0), b = v(
32
- r,
43
+ }), [r.blocInstance]), a = u(0), d = u(0), s = U(
44
+ c,
33
45
  // Get snapshot
34
- () => (u.current++, t.blocInstance.state),
46
+ () => (a.current++, r.blocInstance.state),
35
47
  // Get server snapshot (same as client for now)
36
48
  () => {
37
- const o = t.blocInstance;
38
- return a.current++, o.state;
49
+ const l = r.blocInstance;
50
+ return d.current++, l.state;
39
51
  }
40
- ), S = n(0), i = l(() => (S.current++, t.getProxyState(b)), [b]), x = n(0), h = l(() => (x.current++, t.getProxyBlocInstance()), [t.blocInstance]);
41
- return B(() => {
42
- t.updateLastNotified(d.current);
43
- }), [i, h];
52
+ ), S = u(0), I = b(() => (S.current++, r.getProxyState(s)), [s]), y = u(0), C = b(() => (y.current++, r.getProxyBlocInstance()), [r.blocInstance]);
53
+ return M(() => {
54
+ r.updateLastNotified(f.current);
55
+ }), [I, C];
44
56
  }
45
- function M(c, e = {}) {
46
- const f = n(`external-${g()}`), d = n(/* @__PURE__ */ new Set()), t = n(/* @__PURE__ */ new Set()), s = n(null), m = l(() => {
47
- const r = w.getInstance();
48
- if (c.constructor.isolated || c.isolated) {
49
- const a = new c(e == null ? void 0 : e.props), b = (e == null ? void 0 : e.id) || `${c.name}_${g()}`;
50
- return a._updateId(b), r.activateBloc(a), a;
57
+ function K(n, e = {}) {
58
+ const m = u(`external-${h()}`), f = u(/* @__PURE__ */ new Set()), r = u(/* @__PURE__ */ new Set()), t = u(null), x = b(() => {
59
+ const c = w.getInstance();
60
+ if (n.constructor.isolated || n.isolated) {
61
+ const d = new n(e == null ? void 0 : e.props), s = (e == null ? void 0 : e.id) || `${n.name}_${h()}`;
62
+ return d._updateId(s), c.activateBloc(d), d;
51
63
  }
52
- return r.getBloc(c, {
64
+ return c.getBloc(n, {
53
65
  id: e == null ? void 0 : e.id,
54
66
  props: e == null ? void 0 : e.props
55
67
  });
56
- }, [c, e == null ? void 0 : e.id]), I = l(
68
+ }, [n, e == null ? void 0 : e.id]), B = b(
57
69
  () => ({
58
70
  getSnapshot: () => {
59
- const r = s.current;
60
- return r ? r.state : void 0;
71
+ const c = t.current;
72
+ return c ? c.state : void 0;
61
73
  },
62
- subscribe: (r) => {
63
- const u = s.current;
64
- if (!u)
74
+ subscribe: (c) => {
75
+ const a = t.current;
76
+ if (!a)
65
77
  return () => {
66
78
  };
67
- const a = (S, i) => {
79
+ const d = (S, I) => {
68
80
  try {
69
- d.current.clear(), t.current.clear(), e.selector && u && e.selector(S, i, u), r.length > 0 ? r(S) : r();
70
- } catch (x) {
71
- console.error("Listener error in useExternalBlocStore:", x);
81
+ f.current.clear(), r.current.clear(), e.selector && a && e.selector(S, I, a), c.length > 0 ? c(S) : c();
82
+ } catch (y) {
83
+ console.error("Listener error in useExternalBlocStore:", y);
72
84
  }
73
85
  };
74
- return u._observer.subscribe({
75
- id: f.current,
76
- fn: a
86
+ return a._observer.subscribe({
87
+ id: m.current,
88
+ fn: d
77
89
  });
78
90
  },
79
91
  getServerSnapshot: () => {
80
- const r = s.current;
81
- return r ? r.state : void 0;
92
+ const c = t.current;
93
+ return c ? c.state : void 0;
82
94
  }
83
95
  }),
84
96
  []
85
97
  );
86
- return s.current = m, {
87
- externalStore: I,
88
- instance: s,
89
- usedKeys: d,
90
- usedClassPropKeys: t,
91
- rid: f.current
98
+ return t.current = x, {
99
+ externalStore: B,
100
+ instance: t,
101
+ usedKeys: f,
102
+ usedClassPropKeys: r,
103
+ rid: m.current
92
104
  };
93
105
  }
94
106
  export {
95
- K as useBloc,
96
- M as useExternalBlocStore
107
+ P as useBloc,
108
+ P as useBlocNext,
109
+ K as useExternalBlocStore
97
110
  };
98
111
  //# sourceMappingURL=index.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n AdapterOptions,\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: AdapterOptions<InstanceType<B>>,\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Track adapter creation\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n options,\n );\n return newAdapter;\n }, []);\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetConsumerTracking();\n\n // Track options changes\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = options;\n }, [options]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter.blocInstance]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter.blocInstance]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n const proxyState = adapter.getProxyState(rawState);\n return proxyState;\n }, [rawState]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getProxyBlocInstance();\n return proxyBloc;\n }, [adapter.blocInstance]);\n\n // Mark consumer as rendered after each render\n useEffect(() => {\n adapter.updateLastNotified(componentRef.current);\n });\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n InferPropsFromGeneric,\n generateUUID,\n} from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n props?: InferPropsFromGeneric<B>;\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(options?.props) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._updateId(uniqueId);\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n props: options?.props,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n const unsubscribe = currentInstance._observer.subscribe({\n id: ridRef.current,\n fn: safeListener,\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","adapter","useMemo","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n AdapterOptions,\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: {\n props?: ConstructorParameters<B>[0];\n id?: string;\n dependencies?: (bloc: InstanceType<B>) => unknown[];\n onMount?: (bloc: InstanceType<B>) => void;\n onUnmount?: (bloc: InstanceType<B>) => void;\n },\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Track adapter creation\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n },\n );\n return newAdapter;\n }, []);\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetConsumerTracking();\n\n // Track options changes and update props\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n };\n\n if (options?.props !== undefined) {\n adapter.updateProps(options.props);\n }\n }, [options?.props, options?.id, options?.dependencies, options?.onMount, options?.onUnmount]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter.blocInstance]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter.blocInstance]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n const proxyState = adapter.getProxyState(rawState);\n return proxyState;\n }, [rawState]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getProxyBlocInstance();\n return proxyBloc;\n }, [adapter.blocInstance]);\n\n // Mark consumer as rendered after each render\n useEffect(() => {\n adapter.updateLastNotified(componentRef.current);\n });\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n InferPropsFromGeneric,\n generateUUID,\n} from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n props?: InferPropsFromGeneric<B>;\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(options?.props) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._updateId(uniqueId);\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n props: options?.props,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n const unsubscribe = currentInstance._observer.subscribe({\n id: ridRef.current,\n fn: safeListener,\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","adapter","useMemo","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error"],"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-10",
3
+ "version": "2.0.0-rn-12",
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-10"
33
+ "@blac/core": "2.0.0-rn-12"
34
34
  },
35
35
  "peerDependenciesMeta": {
36
36
  "@types/react": {
@@ -0,0 +1,413 @@
1
+ import { Blac, Bloc, Cubit, PropsUpdated } from '@blac/core';
2
+ import { act, renderHook, waitFor } from '@testing-library/react';
3
+ import React from 'react';
4
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
5
+ import useBloc from '../useBloc';
6
+
7
+ // Test components
8
+ interface SearchProps {
9
+ query: string;
10
+ limit?: number;
11
+ }
12
+
13
+ interface SearchState {
14
+ results: string[];
15
+ loading: boolean;
16
+ }
17
+
18
+ class SearchBloc extends Bloc<SearchState, PropsUpdated<SearchProps>> {
19
+ constructor(private config: { apiEndpoint: string }) {
20
+ super({ results: [], loading: false });
21
+
22
+ this.on(PropsUpdated<SearchProps>, (event, emit) => {
23
+ emit({ results: [`Search: ${event.props.query}`], loading: false });
24
+ });
25
+ }
26
+ }
27
+
28
+ interface CounterProps {
29
+ step: number;
30
+ }
31
+
32
+ interface CounterState {
33
+ count: number;
34
+ stepSize: number;
35
+ }
36
+
37
+ class CounterCubit extends Cubit<CounterState, CounterProps> {
38
+ constructor() {
39
+ super({ count: 0, stepSize: 1 });
40
+ }
41
+
42
+ protected onPropsChanged(oldProps: CounterProps | undefined, newProps: CounterProps): void {
43
+ if (oldProps?.step !== newProps.step) {
44
+ this.emit({ ...this.state, stepSize: newProps.step });
45
+ }
46
+ }
47
+
48
+ increment = () => {
49
+ const step = this.props?.step ?? 1;
50
+ this.emit({ ...this.state, count: this.state.count + step, stepSize: step });
51
+ };
52
+ }
53
+
54
+ describe('useBloc props integration', () => {
55
+ beforeEach(() => {
56
+ Blac.resetInstance();
57
+ });
58
+
59
+ describe('Basic props functionality', () => {
60
+ it('should pass props to Bloc via adapter', async () => {
61
+ const { result } = renderHook(
62
+ ({ query }) => useBloc(
63
+ SearchBloc,
64
+ { props: { apiEndpoint: '/api/search', query } }
65
+ ),
66
+ { initialProps: { query: 'initial' } }
67
+ );
68
+
69
+ await act(async () => {
70
+ await new Promise(resolve => setTimeout(resolve, 0));
71
+ });
72
+
73
+ const [state] = result.current;
74
+ expect(state.results).toEqual(['Search: initial']);
75
+ });
76
+
77
+ it('should update props when they change', async () => {
78
+ const { result, rerender } = renderHook(
79
+ ({ query }) => useBloc(
80
+ SearchBloc,
81
+ { props: { apiEndpoint: '/api/search', query } }
82
+ ),
83
+ { initialProps: { query: 'initial' } }
84
+ );
85
+
86
+ await act(async () => {
87
+ await new Promise(resolve => setTimeout(resolve, 0));
88
+ });
89
+
90
+ expect(result.current[0].results).toEqual(['Search: initial']);
91
+
92
+ rerender({ query: 'updated' });
93
+
94
+ await act(async () => {
95
+ await new Promise(resolve => setTimeout(resolve, 0));
96
+ });
97
+
98
+ expect(result.current[0].results).toEqual(['Search: updated']);
99
+ });
100
+
101
+ it('should work with Cubit props', async () => {
102
+ const { result } = renderHook(
103
+ ({ step }) => {
104
+ const [state, cubit] = useBloc(
105
+ CounterCubit,
106
+ { props: { step } }
107
+ );
108
+ // Access count to ensure it's tracked
109
+ void state.count;
110
+ return [state, cubit];
111
+ },
112
+ { initialProps: { step: 1 } }
113
+ );
114
+
115
+ // Wait for props to be set
116
+ await act(async () => {
117
+ await new Promise(resolve => setTimeout(resolve, 0));
118
+ });
119
+
120
+ const [state, cubit] = result.current;
121
+ expect(state.stepSize).toBe(1);
122
+
123
+ act(() => {
124
+ cubit.increment();
125
+ });
126
+
127
+ // Wait for React to re-render with new state
128
+ await waitFor(() => {
129
+ expect(result.current[0].count).toBe(1);
130
+ });
131
+ });
132
+
133
+ it('should update Cubit props reactively', async () => {
134
+ const { result, rerender } = renderHook(
135
+ ({ step }) => {
136
+ const [state, cubit] = useBloc(
137
+ CounterCubit,
138
+ { props: { step } }
139
+ );
140
+ // Access count to ensure it's tracked
141
+ void state.count;
142
+ return [state, cubit];
143
+ },
144
+ { initialProps: { step: 1 } }
145
+ );
146
+
147
+ // Wait for initial props to be set
148
+ await act(async () => {
149
+ await new Promise(resolve => setTimeout(resolve, 0));
150
+ });
151
+
152
+ expect(result.current[0].stepSize).toBe(1);
153
+
154
+ rerender({ step: 5 });
155
+
156
+ // Wait for props update
157
+ await act(async () => {
158
+ await new Promise(resolve => setTimeout(resolve, 0));
159
+ });
160
+
161
+ expect(result.current[0].stepSize).toBe(5);
162
+
163
+ act(() => {
164
+ result.current[1].increment();
165
+ });
166
+
167
+ // Wait for React to re-render with new state
168
+ await waitFor(() => {
169
+ expect(result.current[0].count).toBe(5);
170
+ });
171
+ });
172
+ });
173
+
174
+ describe('Props ownership', () => {
175
+ it('should enforce single owner for props', async () => {
176
+ const warnSpy = vi.spyOn(Blac, 'warn').mockImplementation(() => {});
177
+
178
+ // First hook owns props
179
+ const { result: result1 } = renderHook(() =>
180
+ useBloc(
181
+ SearchBloc,
182
+ { props: { apiEndpoint: '/api/search', query: 'owner' } }
183
+ )
184
+ );
185
+
186
+ // Second hook cannot override props
187
+ const { result: result2 } = renderHook(() =>
188
+ useBloc(
189
+ SearchBloc,
190
+ { props: { apiEndpoint: '/api/search', query: 'hijacker' } }
191
+ )
192
+ );
193
+
194
+ await act(async () => {
195
+ await new Promise(resolve => setTimeout(resolve, 0));
196
+ });
197
+
198
+ expect(warnSpy).toHaveBeenCalledWith(
199
+ expect.stringContaining('non-owner adapter')
200
+ );
201
+
202
+ // Both should see the owner's state
203
+ expect(result1.current[0].results).toEqual(['Search: owner']);
204
+ expect(result2.current[0].results).toEqual(['Search: owner']);
205
+
206
+ warnSpy.mockRestore();
207
+ });
208
+
209
+ it('should allow read-only consumers without props', async () => {
210
+ // Owner with props
211
+ const { result: ownerResult } = renderHook(() =>
212
+ useBloc(
213
+ SearchBloc,
214
+ { props: { apiEndpoint: '/api/search', query: 'test' } }
215
+ )
216
+ );
217
+
218
+ // Read-only consumer without props
219
+ const { result: readerResult } = renderHook(() =>
220
+ useBloc(SearchBloc, { props: { apiEndpoint: '/api/search' } })
221
+ );
222
+
223
+ await act(async () => {
224
+ await new Promise(resolve => setTimeout(resolve, 0));
225
+ });
226
+
227
+ // Both see the same state (but different object references due to proxy)
228
+ expect(ownerResult.current[0]).toEqual(readerResult.current[0]);
229
+ expect(readerResult.current[0].results).toEqual(['Search: test']);
230
+ });
231
+
232
+ it('should transfer ownership when owner unmounts', async () => {
233
+ const { result: result1, unmount: unmount1 } = renderHook(() =>
234
+ useBloc(
235
+ SearchBloc,
236
+ { props: { apiEndpoint: '/api/search', query: 'first' } }
237
+ )
238
+ );
239
+
240
+ await act(async () => {
241
+ await new Promise(resolve => setTimeout(resolve, 0));
242
+ });
243
+
244
+ expect(result1.current[0].results).toEqual(['Search: first']);
245
+
246
+ // Unmount first owner
247
+ unmount1();
248
+
249
+ // New owner can take over
250
+ const { result: result2 } = renderHook(() =>
251
+ useBloc(
252
+ SearchBloc,
253
+ { props: { apiEndpoint: '/api/search', query: 'second' } }
254
+ )
255
+ );
256
+
257
+ await act(async () => {
258
+ await new Promise(resolve => setTimeout(resolve, 0));
259
+ });
260
+
261
+ expect(result2.current[0].results).toEqual(['Search: second']);
262
+ });
263
+ });
264
+
265
+ describe('Props with other options', () => {
266
+ it('should work with key option', async () => {
267
+ const { result } = renderHook(() =>
268
+ useBloc(
269
+ SearchBloc,
270
+ {
271
+ id: 'custom-search',
272
+ props: { apiEndpoint: '/api/search', query: 'test' }
273
+ }
274
+ )
275
+ );
276
+
277
+ await act(async () => {
278
+ await new Promise(resolve => setTimeout(resolve, 0));
279
+ });
280
+
281
+ expect(result.current[0].results).toEqual(['Search: test']);
282
+ expect(result.current[1]._id).toBe('custom-search');
283
+ });
284
+
285
+ it('should work with lifecycle hooks', async () => {
286
+ const onMount = vi.fn();
287
+ const onUnmount = vi.fn();
288
+
289
+ const { result, unmount } = renderHook(() =>
290
+ useBloc(
291
+ SearchBloc,
292
+ {
293
+ props: { apiEndpoint: '/api/search', query: 'test' },
294
+ onMount,
295
+ onUnmount
296
+ }
297
+ )
298
+ );
299
+
300
+ expect(onMount).toHaveBeenCalledWith(result.current[1]);
301
+
302
+ unmount();
303
+
304
+ expect(onUnmount).toHaveBeenCalledWith(result.current[1]);
305
+ });
306
+
307
+ it('should work with manual dependencies', () => {
308
+ const { result, rerender } = renderHook(
309
+ ({ step }) => useBloc(
310
+ CounterCubit,
311
+ {
312
+ props: { step },
313
+ dependencies: (cubit) => [cubit.state.count]
314
+ }
315
+ ),
316
+ { initialProps: { step: 1 } }
317
+ );
318
+
319
+ const [, cubit] = result.current;
320
+
321
+ // Increment should trigger re-render (count is a dependency)
322
+ act(() => {
323
+ cubit.increment();
324
+ });
325
+
326
+ expect(result.current[0].count).toBe(1);
327
+
328
+ // Changing props should also work
329
+ rerender({ step: 2 });
330
+ expect(result.current[0].stepSize).toBe(2);
331
+ });
332
+ });
333
+
334
+ describe('Edge cases', () => {
335
+ it('should handle undefined props', async () => {
336
+ const { result } = renderHook(() => {
337
+ const [state, cubit] = useBloc(
338
+ CounterCubit,
339
+ { props: undefined }
340
+ );
341
+ // Access count to ensure it's tracked
342
+ void state.count;
343
+ return [state, cubit];
344
+ });
345
+
346
+ // Wait for any effects
347
+ await act(async () => {
348
+ await new Promise(resolve => setTimeout(resolve, 0));
349
+ });
350
+
351
+ const [state, cubit] = result.current;
352
+ expect(state.stepSize).toBe(1);
353
+
354
+ act(() => {
355
+ cubit.increment();
356
+ });
357
+
358
+ // Wait for React to re-render with new state
359
+ await waitFor(() => {
360
+ expect(result.current[0].count).toBe(1); // Uses default step
361
+ });
362
+ });
363
+
364
+ it('should not re-render for unchanged props', () => {
365
+ let renderCount = 0;
366
+
367
+ const { rerender } = renderHook(
368
+ ({ query }) => {
369
+ renderCount++;
370
+ return useBloc(
371
+ SearchBloc,
372
+ { props: { apiEndpoint: '/api/search', query } }
373
+ );
374
+ },
375
+ { initialProps: { query: 'test' } }
376
+ );
377
+
378
+ const initialRenderCount = renderCount;
379
+
380
+ // Rerender with same props
381
+ rerender({ query: 'test' });
382
+
383
+ // Should not cause additional renders beyond React's normal behavior
384
+ expect(renderCount).toBe(initialRenderCount + 1);
385
+ });
386
+
387
+ it('should handle rapid props updates', async () => {
388
+ const { result, rerender } = renderHook(
389
+ ({ query }) => useBloc(
390
+ SearchBloc,
391
+ { props: { apiEndpoint: '/api/search', query } }
392
+ ),
393
+ { initialProps: { query: 'initial' } }
394
+ );
395
+
396
+ // Wait for initial render
397
+ await act(async () => {
398
+ await new Promise(resolve => setTimeout(resolve, 0));
399
+ });
400
+
401
+ // Rapid updates
402
+ await act(async () => {
403
+ rerender({ query: 'update1' });
404
+ rerender({ query: 'update2' });
405
+ rerender({ query: 'update3' });
406
+ await new Promise(resolve => setTimeout(resolve, 50));
407
+ });
408
+
409
+ // Should have the latest value
410
+ expect(result.current[0].results).toEqual(['Search: update3']);
411
+ });
412
+ });
413
+ });
@@ -0,0 +1,228 @@
1
+ import { Blac, Cubit } from '@blac/core';
2
+ import { renderHook, act } from '@testing-library/react';
3
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
4
+ import useBloc from '../useBloc';
5
+
6
+ interface TestState {
7
+ count: number;
8
+ nested: {
9
+ value: string;
10
+ };
11
+ }
12
+
13
+ class TestCubit extends Cubit<TestState> {
14
+ constructor() {
15
+ super({ count: 0, nested: { value: 'initial' } });
16
+ }
17
+
18
+ increment = () => {
19
+ this.emit({ ...this.state, count: this.state.count + 1 });
20
+ };
21
+
22
+ updateNested = () => {
23
+ this.emit({ ...this.state, nested: { value: 'updated' } });
24
+ };
25
+ }
26
+
27
+ describe('useBloc with proxy tracking config', () => {
28
+ const originalConfig = { ...Blac.config };
29
+
30
+ beforeEach(() => {
31
+ Blac.resetInstance();
32
+ });
33
+
34
+ afterEach(() => {
35
+ Blac.setConfig(originalConfig);
36
+ Blac.resetInstance();
37
+ });
38
+
39
+ describe('when proxyDependencyTracking is enabled (default)', () => {
40
+ beforeEach(() => {
41
+ Blac.setConfig({ proxyDependencyTracking: true });
42
+ });
43
+
44
+ it('should only re-render when accessed properties change', () => {
45
+ const { result } = renderHook(() => useBloc(TestCubit));
46
+ const renderSpy = vi.fn();
47
+
48
+ // Access only count in render
49
+ renderHook(() => {
50
+ const [state] = useBloc(TestCubit);
51
+ renderSpy(state.count);
52
+ return state.count;
53
+ });
54
+
55
+ expect(renderSpy).toHaveBeenCalledTimes(1);
56
+ expect(renderSpy).toHaveBeenCalledWith(0);
57
+
58
+ // Update nested value (not accessed)
59
+ act(() => {
60
+ result.current[1].updateNested();
61
+ });
62
+
63
+ // Should not re-render since nested.value wasn't accessed
64
+ expect(renderSpy).toHaveBeenCalledTimes(1);
65
+
66
+ // Update count (accessed property)
67
+ act(() => {
68
+ result.current[1].increment();
69
+ });
70
+
71
+ // Should re-render since count was accessed
72
+ expect(renderSpy).toHaveBeenCalledTimes(2);
73
+ expect(renderSpy).toHaveBeenLastCalledWith(1);
74
+ });
75
+ });
76
+
77
+ describe('when proxyDependencyTracking is disabled', () => {
78
+ beforeEach(() => {
79
+ Blac.setConfig({ proxyDependencyTracking: false });
80
+ });
81
+
82
+ it('should re-render on any state change', () => {
83
+ const { result } = renderHook(() => useBloc(TestCubit));
84
+ const renderSpy = vi.fn();
85
+
86
+ // Access only count in render
87
+ renderHook(() => {
88
+ const [state] = useBloc(TestCubit);
89
+ renderSpy(state.count);
90
+ return state.count;
91
+ });
92
+
93
+ expect(renderSpy).toHaveBeenCalledTimes(1);
94
+ expect(renderSpy).toHaveBeenCalledWith(0);
95
+
96
+ // Update nested value (not accessed)
97
+ act(() => {
98
+ result.current[1].updateNested();
99
+ });
100
+
101
+ // Should re-render even though nested.value wasn't accessed
102
+ expect(renderSpy).toHaveBeenCalledTimes(2);
103
+ expect(renderSpy).toHaveBeenLastCalledWith(0); // count didn't change
104
+
105
+ // Update count
106
+ act(() => {
107
+ result.current[1].increment();
108
+ });
109
+
110
+ // Should re-render
111
+ expect(renderSpy).toHaveBeenCalledTimes(3);
112
+ expect(renderSpy).toHaveBeenLastCalledWith(1);
113
+ });
114
+
115
+ it('should still respect manual dependencies when provided', () => {
116
+ const renderSpy = vi.fn();
117
+
118
+ const { result } = renderHook(() =>
119
+ useBloc(TestCubit, {
120
+ dependencies: (bloc) => [bloc.state.count]
121
+ })
122
+ );
123
+
124
+ // Create another hook that tracks renders
125
+ renderHook(() => {
126
+ const [state] = useBloc(TestCubit, {
127
+ dependencies: (bloc) => [bloc.state.count]
128
+ });
129
+ renderSpy(state);
130
+ return state;
131
+ });
132
+
133
+ expect(renderSpy).toHaveBeenCalledTimes(1);
134
+
135
+ // Update nested value
136
+ act(() => {
137
+ result.current[1].updateNested();
138
+ });
139
+
140
+ // Should not re-render since dependencies only include count
141
+ expect(renderSpy).toHaveBeenCalledTimes(1);
142
+
143
+ // Update count
144
+ act(() => {
145
+ result.current[1].increment();
146
+ });
147
+
148
+ // Should re-render since count is in dependencies
149
+ expect(renderSpy).toHaveBeenCalledTimes(2);
150
+ });
151
+ });
152
+
153
+ describe('runtime config changes', () => {
154
+ it('should respect config changes for new hooks', () => {
155
+ // Start with proxy tracking enabled
156
+ Blac.setConfig({ proxyDependencyTracking: true });
157
+
158
+ // Create a new cubit instance for this test to avoid interference
159
+ class TestCubit2 extends Cubit<TestState> {
160
+ constructor() {
161
+ super({ count: 0, nested: { value: 'initial' } });
162
+ }
163
+
164
+ increment = () => {
165
+ this.emit({ ...this.state, count: this.state.count + 1 });
166
+ };
167
+
168
+ updateNested = () => {
169
+ this.emit({ ...this.state, nested: { value: 'updated' } });
170
+ };
171
+ }
172
+
173
+ const renderSpy1 = vi.fn();
174
+
175
+ const { result: result1 } = renderHook(() => {
176
+ const [state, bloc] = useBloc(TestCubit2);
177
+ renderSpy1(state.count);
178
+ return [state, bloc] as const;
179
+ });
180
+
181
+ expect(renderSpy1).toHaveBeenCalledTimes(1);
182
+
183
+ // Update nested value
184
+ act(() => {
185
+ result1.current[1].updateNested();
186
+ });
187
+
188
+ // Should not re-render since nested.value wasn't accessed (proxy tracking enabled)
189
+ expect(renderSpy1).toHaveBeenCalledTimes(1);
190
+
191
+ // Change config for future hooks
192
+ Blac.setConfig({ proxyDependencyTracking: false });
193
+
194
+ // Create new cubit class to ensure fresh instance
195
+ class TestCubit3 extends Cubit<TestState> {
196
+ constructor() {
197
+ super({ count: 0, nested: { value: 'initial' } });
198
+ }
199
+
200
+ increment = () => {
201
+ this.emit({ ...this.state, count: this.state.count + 1 });
202
+ };
203
+
204
+ updateNested = () => {
205
+ this.emit({ ...this.state, nested: { value: 'updated' } });
206
+ };
207
+ }
208
+
209
+ // Create new hook after config change
210
+ const renderSpy2 = vi.fn();
211
+ const { result: result2 } = renderHook(() => {
212
+ const [state, bloc] = useBloc(TestCubit3);
213
+ renderSpy2(state.count);
214
+ return [state, bloc] as const;
215
+ });
216
+
217
+ expect(renderSpy2).toHaveBeenCalledTimes(1);
218
+
219
+ // Update nested value on the new cubit
220
+ act(() => {
221
+ result2.current[1].updateNested();
222
+ });
223
+
224
+ // Should re-render even though nested.value wasn't accessed (proxy tracking disabled)
225
+ expect(renderSpy2).toHaveBeenCalledTimes(2);
226
+ });
227
+ });
228
+ });
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default as useBloc } from './useBloc';
2
+ export { default as useBlocNext } from './useBloc';
2
3
  export { default as useExternalBlocStore } from './useExternalBlocStore';
package/src/useBloc.ts CHANGED
@@ -20,7 +20,13 @@ type HookTypes<B extends BlocConstructor<BlocBase<any>>> = [
20
20
  */
21
21
  function useBloc<B extends BlocConstructor<BlocBase<any>>>(
22
22
  blocConstructor: B,
23
- options?: AdapterOptions<InstanceType<B>>,
23
+ options?: {
24
+ props?: ConstructorParameters<B>[0];
25
+ id?: string;
26
+ dependencies?: (bloc: InstanceType<B>) => unknown[];
27
+ onMount?: (bloc: InstanceType<B>) => void;
28
+ onUnmount?: (bloc: InstanceType<B>) => void;
29
+ },
24
30
  ): HookTypes<B> {
25
31
  // Create a unique identifier for this hook instance
26
32
  const renderCount = useRef(0);
@@ -35,7 +41,13 @@ function useBloc<B extends BlocConstructor<BlocBase<any>>>(
35
41
  componentRef: componentRef,
36
42
  blocConstructor,
37
43
  },
38
- options,
44
+ {
45
+ id: options?.id,
46
+ dependencies: options?.dependencies,
47
+ props: options?.props,
48
+ onMount: options?.onMount,
49
+ onUnmount: options?.onUnmount,
50
+ },
39
51
  );
40
52
  return newAdapter;
41
53
  }, []);
@@ -44,12 +56,22 @@ function useBloc<B extends BlocConstructor<BlocBase<any>>>(
44
56
  // properties accessed during the current render
45
57
  adapter.resetConsumerTracking();
46
58
 
47
- // Track options changes
59
+ // Track options changes and update props
48
60
  const optionsChangeCount = useRef(0);
49
61
  useEffect(() => {
50
62
  optionsChangeCount.current++;
51
- adapter.options = options;
52
- }, [options]);
63
+ adapter.options = {
64
+ id: options?.id,
65
+ dependencies: options?.dependencies,
66
+ props: options?.props,
67
+ onMount: options?.onMount,
68
+ onUnmount: options?.onUnmount,
69
+ };
70
+
71
+ if (options?.props !== undefined) {
72
+ adapter.updateProps(options.props);
73
+ }
74
+ }, [options?.props, options?.id, options?.dependencies, options?.onMount, options?.onUnmount]);
53
75
 
54
76
  // Register as consumer and handle lifecycle
55
77
  const mountEffectCount = useRef(0);