@blac/react 2.0.0-rc.16 → 2.0.0-rc.17
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 +63 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -20
- package/dist/index.d.ts +28 -20
- package/dist/index.js +64 -38
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -84,9 +84,11 @@ function determineTrackingMode(options) {
|
|
|
84
84
|
* - **Manual dependencies**: Explicit dependency array like useEffect
|
|
85
85
|
* - **No tracking**: Returns full state without optimization
|
|
86
86
|
*
|
|
87
|
-
*
|
|
87
|
+
* **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).
|
|
88
|
+
* Use `useBlocActions()` instead for stateless containers.
|
|
89
|
+
*
|
|
88
90
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
89
|
-
* @param BlocClass - The state container class to connect to
|
|
91
|
+
* @param BlocClass - The state container class to connect to (must not be stateless)
|
|
90
92
|
* @param options - Configuration options for tracking mode and instance management
|
|
91
93
|
* @returns Tuple with [state, bloc instance, ref]
|
|
92
94
|
*
|
|
@@ -108,37 +110,44 @@ function determineTrackingMode(options) {
|
|
|
108
110
|
* instanceId: 'unique-id'
|
|
109
111
|
* });
|
|
110
112
|
* ```
|
|
111
|
-
*
|
|
112
|
-
* @example With generic bloc and explicit state type
|
|
113
|
-
* ```ts
|
|
114
|
-
* const [state, myBloc] = useBloc<MyStateType>(GenericBloc);
|
|
115
|
-
* // state is typed as MyStateType
|
|
116
|
-
* ```
|
|
117
113
|
*/
|
|
118
114
|
function useBloc(BlocClass, options) {
|
|
119
115
|
const componentRef = (0, react.useRef)({});
|
|
120
|
-
const isIsolated = (0, __blac_core.isIsolatedClass)(BlocClass);
|
|
121
116
|
const initialPropsRef = (0, react.useRef)(options?.props);
|
|
117
|
+
const isStateless = (0, __blac_core.isStatelessClass)(BlocClass);
|
|
118
|
+
const isIsolated = (0, __blac_core.isIsolatedClass)(BlocClass);
|
|
122
119
|
const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = (0, react.useMemo)(() => {
|
|
120
|
+
if (isStateless) {
|
|
121
|
+
console.error(`[BlaC] useBloc() does not support stateless containers. "${BlocClass.name}" is a StatelessCubit or StatelessVertex. Use useBlocActions() instead for stateless containers.`);
|
|
122
|
+
const instance$1 = (0, __blac_core.acquire)(BlocClass, "default");
|
|
123
|
+
return [
|
|
124
|
+
instance$1,
|
|
125
|
+
() => () => {},
|
|
126
|
+
() => ({}),
|
|
127
|
+
void 0,
|
|
128
|
+
null,
|
|
129
|
+
instance$1
|
|
130
|
+
];
|
|
131
|
+
}
|
|
123
132
|
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
124
|
-
const instance =
|
|
133
|
+
const instance = (0, __blac_core.acquire)(BlocClass, instanceKey$1, { props: initialPropsRef.current });
|
|
125
134
|
if (initialPropsRef.current !== void 0) instance.updateProps(initialPropsRef.current);
|
|
126
135
|
const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
|
|
127
136
|
let subscribeFn;
|
|
128
137
|
let getSnapshotFn;
|
|
129
138
|
let adapterState$1;
|
|
130
139
|
if (useManualDeps && options?.dependencies) {
|
|
131
|
-
adapterState$1 = (0, __blac_core.
|
|
132
|
-
subscribeFn = (0, __blac_core.
|
|
133
|
-
getSnapshotFn = (0, __blac_core.
|
|
140
|
+
adapterState$1 = (0, __blac_core.manualDepsInit)(instance);
|
|
141
|
+
subscribeFn = (0, __blac_core.manualDepsSubscribe)(instance, adapterState$1, { dependencies: options.dependencies });
|
|
142
|
+
getSnapshotFn = (0, __blac_core.manualDepsSnapshot)(instance, adapterState$1, { dependencies: options.dependencies });
|
|
134
143
|
} else if (!autoTrackEnabled) {
|
|
135
|
-
adapterState$1 = (0, __blac_core.
|
|
136
|
-
subscribeFn = (0, __blac_core.
|
|
137
|
-
getSnapshotFn = (0, __blac_core.
|
|
144
|
+
adapterState$1 = (0, __blac_core.noTrackInit)(instance);
|
|
145
|
+
subscribeFn = (0, __blac_core.noTrackSubscribe)(instance);
|
|
146
|
+
getSnapshotFn = (0, __blac_core.noTrackSnapshot)(instance);
|
|
138
147
|
} else {
|
|
139
|
-
adapterState$1 = (0, __blac_core.
|
|
140
|
-
subscribeFn = (0, __blac_core.
|
|
141
|
-
getSnapshotFn = (0, __blac_core.
|
|
148
|
+
adapterState$1 = (0, __blac_core.autoTrackInit)(instance);
|
|
149
|
+
subscribeFn = (0, __blac_core.autoTrackSubscribe)(instance, adapterState$1);
|
|
150
|
+
getSnapshotFn = (0, __blac_core.autoTrackSnapshot)(instance, adapterState$1);
|
|
142
151
|
}
|
|
143
152
|
return [
|
|
144
153
|
adapterState$1.proxiedBloc,
|
|
@@ -148,23 +157,35 @@ function useBloc(BlocClass, options) {
|
|
|
148
157
|
adapterState$1,
|
|
149
158
|
instance
|
|
150
159
|
];
|
|
151
|
-
}, [
|
|
160
|
+
}, [
|
|
161
|
+
BlocClass,
|
|
162
|
+
isStateless,
|
|
163
|
+
isIsolated,
|
|
164
|
+
options?.instanceId
|
|
165
|
+
]);
|
|
152
166
|
const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
153
167
|
const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
|
|
154
|
-
const externalDepsManager = (0, react.useRef)(new __blac_core.
|
|
168
|
+
const externalDepsManager = (0, react.useRef)(new __blac_core.ExternalDepsManager());
|
|
155
169
|
(0, react.useEffect)(() => {
|
|
170
|
+
if (isStateless) return;
|
|
156
171
|
if (options?.props !== initialPropsRef.current) rawInstance.updateProps(options?.props);
|
|
157
|
-
}, [
|
|
172
|
+
}, [
|
|
173
|
+
options?.props,
|
|
174
|
+
rawInstance,
|
|
175
|
+
isStateless
|
|
176
|
+
]);
|
|
158
177
|
(0, react.useEffect)(() => {
|
|
178
|
+
if (isStateless || !adapterState) return;
|
|
159
179
|
(0, __blac_core.disableGetterTracking)(adapterState, rawInstance);
|
|
160
|
-
externalDepsManager.current.updateSubscriptions(adapterState.
|
|
180
|
+
externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
|
|
161
181
|
});
|
|
162
182
|
(0, react.useEffect)(() => {
|
|
183
|
+
if (isStateless) return;
|
|
163
184
|
if (options?.onMount) options.onMount(bloc);
|
|
164
185
|
return () => {
|
|
165
186
|
externalDepsManager.current.cleanup();
|
|
166
187
|
if (options?.onUnmount) options.onUnmount(bloc);
|
|
167
|
-
|
|
188
|
+
(0, __blac_core.release)(BlocClass, instanceKey);
|
|
168
189
|
if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
|
|
169
190
|
};
|
|
170
191
|
}, []);
|
|
@@ -181,17 +202,28 @@ function useBloc(BlocClass, options) {
|
|
|
181
202
|
* React hook that connects to a state container instance without triggering re-renders.
|
|
182
203
|
* Use this when you only need to call actions on the bloc without subscribing to state changes.
|
|
183
204
|
*
|
|
184
|
-
*
|
|
205
|
+
* **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).
|
|
206
|
+
* It also works with regular Cubit/Vertex when you don't need state subscriptions.
|
|
207
|
+
*
|
|
185
208
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
186
|
-
* @param BlocClass - The state container class to connect to
|
|
209
|
+
* @param BlocClass - The state container class to connect to (supports both stateful and stateless)
|
|
187
210
|
* @param options - Configuration options for instance management and lifecycle
|
|
188
211
|
* @returns The state container instance for calling actions
|
|
189
212
|
*
|
|
190
|
-
* @example Basic usage
|
|
213
|
+
* @example Basic usage with stateless container
|
|
214
|
+
* ```ts
|
|
215
|
+
* class AnalyticsService extends StatelessCubit {
|
|
216
|
+
* trackEvent(name: string) { ... }
|
|
217
|
+
* }
|
|
218
|
+
*
|
|
219
|
+
* const analytics = useBlocActions(AnalyticsService);
|
|
220
|
+
* analytics.trackEvent('page_view');
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @example With stateful container (no re-renders)
|
|
191
224
|
* ```ts
|
|
192
225
|
* const myBloc = useBlocActions(MyBloc);
|
|
193
|
-
* //
|
|
194
|
-
* myBloc.someMethod();
|
|
226
|
+
* myBloc.someMethod(); // Won't cause re-renders
|
|
195
227
|
* ```
|
|
196
228
|
*
|
|
197
229
|
* @example With isolated instance
|
|
@@ -200,12 +232,6 @@ function useBloc(BlocClass, options) {
|
|
|
200
232
|
* instanceId: 'unique-id'
|
|
201
233
|
* });
|
|
202
234
|
* ```
|
|
203
|
-
*
|
|
204
|
-
* @example With generic bloc and explicit state type
|
|
205
|
-
* ```ts
|
|
206
|
-
* const myBloc = useBlocActions<MyStateType>(GenericBloc);
|
|
207
|
-
* // myBloc.state is typed as MyStateType
|
|
208
|
-
* ```
|
|
209
235
|
*/
|
|
210
236
|
function useBlocActions(BlocClass, options) {
|
|
211
237
|
const componentRef = (0, react.useRef)({});
|
|
@@ -213,13 +239,13 @@ function useBlocActions(BlocClass, options) {
|
|
|
213
239
|
const [bloc, instanceKey] = (0, react.useMemo)(() => {
|
|
214
240
|
const isIsolated = (0, __blac_core.isIsolatedClass)(BlocClass);
|
|
215
241
|
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
216
|
-
return [
|
|
242
|
+
return [(0, __blac_core.acquire)(BlocClass, instanceKey$1, { props: initialPropsRef.current }), instanceKey$1];
|
|
217
243
|
}, [BlocClass]);
|
|
218
244
|
(0, react.useEffect)(() => {
|
|
219
245
|
if (options?.onMount) options.onMount(bloc);
|
|
220
246
|
return () => {
|
|
221
247
|
if (options?.onUnmount) options.onUnmount(bloc);
|
|
222
|
-
|
|
248
|
+
(0, __blac_core.release)(BlocClass, instanceKey);
|
|
223
249
|
if ((0, __blac_core.isIsolatedClass)(BlocClass) && !bloc.isDisposed) bloc.dispose();
|
|
224
250
|
};
|
|
225
251
|
}, []);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["defaultConfig: BlacReactConfig","globalConfig: BlacReactConfig","globalConfig","instanceKey","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<T>","adapterState: AdapterState<T>","adapterState","ExternalDependencyManager","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n ExternalDependencyManager,\n createAutoTrackSubscribe,\n createManualDepsSubscribe,\n createNoTrackSubscribe,\n createAutoTrackSnapshot,\n createManualDepsSnapshot,\n createNoTrackSnapshot,\n initAutoTrackState,\n initManualDepsState,\n initNoTrackState,\n disableGetterTracking,\n isIsolatedClass,\n StateContainerConstructor,\n InstanceState,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * @template S - Optional state type override for generic StateContainers (defaults to never)\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const [state, myBloc] = useBloc<MyStateType>(GenericBloc);\n * // state is typed as MyStateType\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n const componentRef = useRef<ComponentRef>({});\n const isIsolated = isIsolatedClass(BlocClass);\n\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T>,\n TBloc,\n ]\n >(() => {\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = (BlocClass as any).resolve(instanceKey, {\n props: initialPropsRef.current,\n }) as TBloc;\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = initManualDepsState(instance);\n subscribeFn = createManualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = createManualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = initNoTrackState(instance);\n subscribeFn = createNoTrackSubscribe(instance);\n getSnapshotFn = createNoTrackSnapshot(instance);\n } else {\n adapterState = initAutoTrackState(instance);\n subscribeFn = createAutoTrackSubscribe(instance, adapterState);\n getSnapshotFn = createAutoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc!,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDependencyManager());\n\n useEffect(() => {\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance]);\n\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterTracker,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n (BlocClass as any).release(instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n }, []);\n\n return [state, bloc, componentRef];\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport { StateContainerConstructor, isIsolatedClass } from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * @template S - Optional state type override for generic StateContainers (defaults to never)\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options for instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * // Call methods on the bloc without re-rendering\n * myBloc.someMethod();\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const myBloc = useBlocActions<MyStateType>(GenericBloc);\n * // myBloc.state is typed as MyStateType\n * ```\n */\nexport function useBlocActions<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = (BlocClass as any).resolve(instanceKey, {\n props: initialPropsRef.current,\n });\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n (BlocClass as any).release(instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as InstanceType<T>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["defaultConfig: BlacReactConfig","globalConfig: BlacReactConfig","globalConfig","instance","instanceKey","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<T>","adapterState: AdapterState<T>","adapterState","ExternalDepsManager","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n type IsStatelessContainer,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n isIsolatedClass,\n isStatelessClass,\n StateContainerConstructor,\n InstanceState,\n acquire,\n release,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * Type that produces a TypeScript error when a stateless container is used.\n * Evaluates to `never` for stateless containers, causing a type mismatch.\n */\ntype StatefulContainer<T extends StateContainerConstructor> =\n IsStatelessContainer<T> extends true ? never : T;\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).\n * Use `useBlocActions()` instead for stateless containers.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (must not be stateless)\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: StatefulContainer<T>,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n const isStateless = isStatelessClass(BlocClass);\n const isIsolated = isIsolatedClass(BlocClass);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T> | null,\n TBloc,\n ]\n >(() => {\n // Runtime check for stateless containers - log error but don't crash\n if (isStateless) {\n console.error(\n `[BlaC] useBloc() does not support stateless containers. ` +\n `\"${BlocClass.name}\" is a StatelessCubit or StatelessVertex. ` +\n `Use useBlocActions() instead for stateless containers.`,\n );\n const instance = acquire(BlocClass, 'default') as TBloc;\n // Return minimal working functions that won't subscribe to anything\n return [\n instance,\n () => () => {},\n () => ({}) as ExtractState<T>,\n undefined,\n null,\n instance,\n ];\n }\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey, {\n props: initialPropsRef.current,\n }) as TBloc;\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isStateless, isIsolated, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n if (isStateless) return;\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance, isStateless]);\n\n useEffect(() => {\n if (isStateless || !adapterState) return;\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (isStateless) return;\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport {\n StateContainerConstructor,\n isIsolatedClass,\n acquire,\n release,\n} from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).\n * It also works with regular Cubit/Vertex when you don't need state subscriptions.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (supports both stateful and stateless)\n * @param options - Configuration options for instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage with stateless container\n * ```ts\n * class AnalyticsService extends StatelessCubit {\n * trackEvent(name: string) { ... }\n * }\n *\n * const analytics = useBlocActions(AnalyticsService);\n * analytics.trackEvent('page_view');\n * ```\n *\n * @example With stateful container (no re-renders)\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * myBloc.someMethod(); // Won't cause re-renders\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBlocActions<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey, {\n props: initialPropsRef.current,\n });\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as InstanceType<T>;\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtractProps } from '@blac/core';
|
|
2
2
|
import { ExtractState } from '@blac/core';
|
|
3
3
|
import { InstanceReadonlyState } from '@blac/core';
|
|
4
|
+
import { IsStatelessContainer } from '@blac/core';
|
|
4
5
|
import type { RefObject } from 'react';
|
|
5
6
|
import { StateContainerConstructor } from '@blac/core';
|
|
6
7
|
|
|
@@ -33,6 +34,12 @@ export declare function configureBlacReact(config: Partial<BlacReactConfig>): vo
|
|
|
33
34
|
|
|
34
35
|
/* Excluded from this release type: resetBlacReactConfig */
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Type that produces a TypeScript error when a stateless container is used.
|
|
39
|
+
* Evaluates to `never` for stateless containers, causing a type mismatch.
|
|
40
|
+
*/
|
|
41
|
+
declare type StatefulContainer<T extends StateContainerConstructor> = IsStatelessContainer<T> extends true ? never : T;
|
|
42
|
+
|
|
36
43
|
/**
|
|
37
44
|
* React hook that connects a component to a state container with automatic re-render on state changes.
|
|
38
45
|
*
|
|
@@ -41,9 +48,11 @@ export declare function configureBlacReact(config: Partial<BlacReactConfig>): vo
|
|
|
41
48
|
* - **Manual dependencies**: Explicit dependency array like useEffect
|
|
42
49
|
* - **No tracking**: Returns full state without optimization
|
|
43
50
|
*
|
|
44
|
-
*
|
|
51
|
+
* **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).
|
|
52
|
+
* Use `useBlocActions()` instead for stateless containers.
|
|
53
|
+
*
|
|
45
54
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
46
|
-
* @param BlocClass - The state container class to connect to
|
|
55
|
+
* @param BlocClass - The state container class to connect to (must not be stateless)
|
|
47
56
|
* @param options - Configuration options for tracking mode and instance management
|
|
48
57
|
* @returns Tuple with [state, bloc instance, ref]
|
|
49
58
|
*
|
|
@@ -65,30 +74,35 @@ export declare function configureBlacReact(config: Partial<BlacReactConfig>): vo
|
|
|
65
74
|
* instanceId: 'unique-id'
|
|
66
75
|
* });
|
|
67
76
|
* ```
|
|
68
|
-
*
|
|
69
|
-
* @example With generic bloc and explicit state type
|
|
70
|
-
* ```ts
|
|
71
|
-
* const [state, myBloc] = useBloc<MyStateType>(GenericBloc);
|
|
72
|
-
* // state is typed as MyStateType
|
|
73
|
-
* ```
|
|
74
77
|
*/
|
|
75
|
-
export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T
|
|
78
|
+
export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: StatefulContainer<T>, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState<T>>;
|
|
76
79
|
|
|
77
80
|
/**
|
|
78
81
|
* React hook that connects to a state container instance without triggering re-renders.
|
|
79
82
|
* Use this when you only need to call actions on the bloc without subscribing to state changes.
|
|
80
83
|
*
|
|
81
|
-
*
|
|
84
|
+
* **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).
|
|
85
|
+
* It also works with regular Cubit/Vertex when you don't need state subscriptions.
|
|
86
|
+
*
|
|
82
87
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
83
|
-
* @param BlocClass - The state container class to connect to
|
|
88
|
+
* @param BlocClass - The state container class to connect to (supports both stateful and stateless)
|
|
84
89
|
* @param options - Configuration options for instance management and lifecycle
|
|
85
90
|
* @returns The state container instance for calling actions
|
|
86
91
|
*
|
|
87
|
-
* @example Basic usage
|
|
92
|
+
* @example Basic usage with stateless container
|
|
93
|
+
* ```ts
|
|
94
|
+
* class AnalyticsService extends StatelessCubit {
|
|
95
|
+
* trackEvent(name: string) { ... }
|
|
96
|
+
* }
|
|
97
|
+
*
|
|
98
|
+
* const analytics = useBlocActions(AnalyticsService);
|
|
99
|
+
* analytics.trackEvent('page_view');
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* @example With stateful container (no re-renders)
|
|
88
103
|
* ```ts
|
|
89
104
|
* const myBloc = useBlocActions(MyBloc);
|
|
90
|
-
* //
|
|
91
|
-
* myBloc.someMethod();
|
|
105
|
+
* myBloc.someMethod(); // Won't cause re-renders
|
|
92
106
|
* ```
|
|
93
107
|
*
|
|
94
108
|
* @example With isolated instance
|
|
@@ -97,12 +111,6 @@ export declare function useBloc<T extends StateContainerConstructor = StateConta
|
|
|
97
111
|
* instanceId: 'unique-id'
|
|
98
112
|
* });
|
|
99
113
|
* ```
|
|
100
|
-
*
|
|
101
|
-
* @example With generic bloc and explicit state type
|
|
102
|
-
* ```ts
|
|
103
|
-
* const myBloc = useBlocActions<MyStateType>(GenericBloc);
|
|
104
|
-
* // myBloc.state is typed as MyStateType
|
|
105
|
-
* ```
|
|
106
114
|
*/
|
|
107
115
|
export declare function useBlocActions<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocActionsOptions<InstanceType<T>>): InstanceType<T>;
|
|
108
116
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtractProps } from '@blac/core';
|
|
2
2
|
import { ExtractState } from '@blac/core';
|
|
3
3
|
import { InstanceReadonlyState } from '@blac/core';
|
|
4
|
+
import { IsStatelessContainer } from '@blac/core';
|
|
4
5
|
import type { RefObject } from 'react';
|
|
5
6
|
import { StateContainerConstructor } from '@blac/core';
|
|
6
7
|
|
|
@@ -33,6 +34,12 @@ export declare function configureBlacReact(config: Partial<BlacReactConfig>): vo
|
|
|
33
34
|
|
|
34
35
|
/* Excluded from this release type: resetBlacReactConfig */
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Type that produces a TypeScript error when a stateless container is used.
|
|
39
|
+
* Evaluates to `never` for stateless containers, causing a type mismatch.
|
|
40
|
+
*/
|
|
41
|
+
declare type StatefulContainer<T extends StateContainerConstructor> = IsStatelessContainer<T> extends true ? never : T;
|
|
42
|
+
|
|
36
43
|
/**
|
|
37
44
|
* React hook that connects a component to a state container with automatic re-render on state changes.
|
|
38
45
|
*
|
|
@@ -41,9 +48,11 @@ export declare function configureBlacReact(config: Partial<BlacReactConfig>): vo
|
|
|
41
48
|
* - **Manual dependencies**: Explicit dependency array like useEffect
|
|
42
49
|
* - **No tracking**: Returns full state without optimization
|
|
43
50
|
*
|
|
44
|
-
*
|
|
51
|
+
* **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).
|
|
52
|
+
* Use `useBlocActions()` instead for stateless containers.
|
|
53
|
+
*
|
|
45
54
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
46
|
-
* @param BlocClass - The state container class to connect to
|
|
55
|
+
* @param BlocClass - The state container class to connect to (must not be stateless)
|
|
47
56
|
* @param options - Configuration options for tracking mode and instance management
|
|
48
57
|
* @returns Tuple with [state, bloc instance, ref]
|
|
49
58
|
*
|
|
@@ -65,30 +74,35 @@ export declare function configureBlacReact(config: Partial<BlacReactConfig>): vo
|
|
|
65
74
|
* instanceId: 'unique-id'
|
|
66
75
|
* });
|
|
67
76
|
* ```
|
|
68
|
-
*
|
|
69
|
-
* @example With generic bloc and explicit state type
|
|
70
|
-
* ```ts
|
|
71
|
-
* const [state, myBloc] = useBloc<MyStateType>(GenericBloc);
|
|
72
|
-
* // state is typed as MyStateType
|
|
73
|
-
* ```
|
|
74
77
|
*/
|
|
75
|
-
export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T
|
|
78
|
+
export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: StatefulContainer<T>, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState<T>>;
|
|
76
79
|
|
|
77
80
|
/**
|
|
78
81
|
* React hook that connects to a state container instance without triggering re-renders.
|
|
79
82
|
* Use this when you only need to call actions on the bloc without subscribing to state changes.
|
|
80
83
|
*
|
|
81
|
-
*
|
|
84
|
+
* **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).
|
|
85
|
+
* It also works with regular Cubit/Vertex when you don't need state subscriptions.
|
|
86
|
+
*
|
|
82
87
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
83
|
-
* @param BlocClass - The state container class to connect to
|
|
88
|
+
* @param BlocClass - The state container class to connect to (supports both stateful and stateless)
|
|
84
89
|
* @param options - Configuration options for instance management and lifecycle
|
|
85
90
|
* @returns The state container instance for calling actions
|
|
86
91
|
*
|
|
87
|
-
* @example Basic usage
|
|
92
|
+
* @example Basic usage with stateless container
|
|
93
|
+
* ```ts
|
|
94
|
+
* class AnalyticsService extends StatelessCubit {
|
|
95
|
+
* trackEvent(name: string) { ... }
|
|
96
|
+
* }
|
|
97
|
+
*
|
|
98
|
+
* const analytics = useBlocActions(AnalyticsService);
|
|
99
|
+
* analytics.trackEvent('page_view');
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* @example With stateful container (no re-renders)
|
|
88
103
|
* ```ts
|
|
89
104
|
* const myBloc = useBlocActions(MyBloc);
|
|
90
|
-
* //
|
|
91
|
-
* myBloc.someMethod();
|
|
105
|
+
* myBloc.someMethod(); // Won't cause re-renders
|
|
92
106
|
* ```
|
|
93
107
|
*
|
|
94
108
|
* @example With isolated instance
|
|
@@ -97,12 +111,6 @@ export declare function useBloc<T extends StateContainerConstructor = StateConta
|
|
|
97
111
|
* instanceId: 'unique-id'
|
|
98
112
|
* });
|
|
99
113
|
* ```
|
|
100
|
-
*
|
|
101
|
-
* @example With generic bloc and explicit state type
|
|
102
|
-
* ```ts
|
|
103
|
-
* const myBloc = useBlocActions<MyStateType>(GenericBloc);
|
|
104
|
-
* // myBloc.state is typed as MyStateType
|
|
105
|
-
* ```
|
|
106
114
|
*/
|
|
107
115
|
export declare function useBlocActions<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocActionsOptions<InstanceType<T>>): InstanceType<T>;
|
|
108
116
|
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, generateIsolatedKey, isIsolatedClass, isStatelessClass, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, release } from "@blac/core";
|
|
3
3
|
|
|
4
4
|
//#region src/utils/instance-keys.ts
|
|
5
5
|
/**
|
|
@@ -84,9 +84,11 @@ function determineTrackingMode(options) {
|
|
|
84
84
|
* - **Manual dependencies**: Explicit dependency array like useEffect
|
|
85
85
|
* - **No tracking**: Returns full state without optimization
|
|
86
86
|
*
|
|
87
|
-
*
|
|
87
|
+
* **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).
|
|
88
|
+
* Use `useBlocActions()` instead for stateless containers.
|
|
89
|
+
*
|
|
88
90
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
89
|
-
* @param BlocClass - The state container class to connect to
|
|
91
|
+
* @param BlocClass - The state container class to connect to (must not be stateless)
|
|
90
92
|
* @param options - Configuration options for tracking mode and instance management
|
|
91
93
|
* @returns Tuple with [state, bloc instance, ref]
|
|
92
94
|
*
|
|
@@ -108,37 +110,44 @@ function determineTrackingMode(options) {
|
|
|
108
110
|
* instanceId: 'unique-id'
|
|
109
111
|
* });
|
|
110
112
|
* ```
|
|
111
|
-
*
|
|
112
|
-
* @example With generic bloc and explicit state type
|
|
113
|
-
* ```ts
|
|
114
|
-
* const [state, myBloc] = useBloc<MyStateType>(GenericBloc);
|
|
115
|
-
* // state is typed as MyStateType
|
|
116
|
-
* ```
|
|
117
113
|
*/
|
|
118
114
|
function useBloc(BlocClass, options) {
|
|
119
115
|
const componentRef = useRef({});
|
|
120
|
-
const isIsolated = isIsolatedClass(BlocClass);
|
|
121
116
|
const initialPropsRef = useRef(options?.props);
|
|
117
|
+
const isStateless = isStatelessClass(BlocClass);
|
|
118
|
+
const isIsolated = isIsolatedClass(BlocClass);
|
|
122
119
|
const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => {
|
|
120
|
+
if (isStateless) {
|
|
121
|
+
console.error(`[BlaC] useBloc() does not support stateless containers. "${BlocClass.name}" is a StatelessCubit or StatelessVertex. Use useBlocActions() instead for stateless containers.`);
|
|
122
|
+
const instance$1 = acquire(BlocClass, "default");
|
|
123
|
+
return [
|
|
124
|
+
instance$1,
|
|
125
|
+
() => () => {},
|
|
126
|
+
() => ({}),
|
|
127
|
+
void 0,
|
|
128
|
+
null,
|
|
129
|
+
instance$1
|
|
130
|
+
];
|
|
131
|
+
}
|
|
123
132
|
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
124
|
-
const instance = BlocClass
|
|
133
|
+
const instance = acquire(BlocClass, instanceKey$1, { props: initialPropsRef.current });
|
|
125
134
|
if (initialPropsRef.current !== void 0) instance.updateProps(initialPropsRef.current);
|
|
126
135
|
const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
|
|
127
136
|
let subscribeFn;
|
|
128
137
|
let getSnapshotFn;
|
|
129
138
|
let adapterState$1;
|
|
130
139
|
if (useManualDeps && options?.dependencies) {
|
|
131
|
-
adapterState$1 =
|
|
132
|
-
subscribeFn =
|
|
133
|
-
getSnapshotFn =
|
|
140
|
+
adapterState$1 = manualDepsInit(instance);
|
|
141
|
+
subscribeFn = manualDepsSubscribe(instance, adapterState$1, { dependencies: options.dependencies });
|
|
142
|
+
getSnapshotFn = manualDepsSnapshot(instance, adapterState$1, { dependencies: options.dependencies });
|
|
134
143
|
} else if (!autoTrackEnabled) {
|
|
135
|
-
adapterState$1 =
|
|
136
|
-
subscribeFn =
|
|
137
|
-
getSnapshotFn =
|
|
144
|
+
adapterState$1 = noTrackInit(instance);
|
|
145
|
+
subscribeFn = noTrackSubscribe(instance);
|
|
146
|
+
getSnapshotFn = noTrackSnapshot(instance);
|
|
138
147
|
} else {
|
|
139
|
-
adapterState$1 =
|
|
140
|
-
subscribeFn =
|
|
141
|
-
getSnapshotFn =
|
|
148
|
+
adapterState$1 = autoTrackInit(instance);
|
|
149
|
+
subscribeFn = autoTrackSubscribe(instance, adapterState$1);
|
|
150
|
+
getSnapshotFn = autoTrackSnapshot(instance, adapterState$1);
|
|
142
151
|
}
|
|
143
152
|
return [
|
|
144
153
|
adapterState$1.proxiedBloc,
|
|
@@ -148,23 +157,35 @@ function useBloc(BlocClass, options) {
|
|
|
148
157
|
adapterState$1,
|
|
149
158
|
instance
|
|
150
159
|
];
|
|
151
|
-
}, [
|
|
160
|
+
}, [
|
|
161
|
+
BlocClass,
|
|
162
|
+
isStateless,
|
|
163
|
+
isIsolated,
|
|
164
|
+
options?.instanceId
|
|
165
|
+
]);
|
|
152
166
|
const state = useSyncExternalStore(subscribe, getSnapshot);
|
|
153
167
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
154
|
-
const externalDepsManager = useRef(new
|
|
168
|
+
const externalDepsManager = useRef(new ExternalDepsManager());
|
|
155
169
|
useEffect(() => {
|
|
170
|
+
if (isStateless) return;
|
|
156
171
|
if (options?.props !== initialPropsRef.current) rawInstance.updateProps(options?.props);
|
|
157
|
-
}, [
|
|
172
|
+
}, [
|
|
173
|
+
options?.props,
|
|
174
|
+
rawInstance,
|
|
175
|
+
isStateless
|
|
176
|
+
]);
|
|
158
177
|
useEffect(() => {
|
|
178
|
+
if (isStateless || !adapterState) return;
|
|
159
179
|
disableGetterTracking(adapterState, rawInstance);
|
|
160
|
-
externalDepsManager.current.updateSubscriptions(adapterState.
|
|
180
|
+
externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
|
|
161
181
|
});
|
|
162
182
|
useEffect(() => {
|
|
183
|
+
if (isStateless) return;
|
|
163
184
|
if (options?.onMount) options.onMount(bloc);
|
|
164
185
|
return () => {
|
|
165
186
|
externalDepsManager.current.cleanup();
|
|
166
187
|
if (options?.onUnmount) options.onUnmount(bloc);
|
|
167
|
-
|
|
188
|
+
release(BlocClass, instanceKey);
|
|
168
189
|
if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
|
|
169
190
|
};
|
|
170
191
|
}, []);
|
|
@@ -181,17 +202,28 @@ function useBloc(BlocClass, options) {
|
|
|
181
202
|
* React hook that connects to a state container instance without triggering re-renders.
|
|
182
203
|
* Use this when you only need to call actions on the bloc without subscribing to state changes.
|
|
183
204
|
*
|
|
184
|
-
*
|
|
205
|
+
* **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).
|
|
206
|
+
* It also works with regular Cubit/Vertex when you don't need state subscriptions.
|
|
207
|
+
*
|
|
185
208
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
186
|
-
* @param BlocClass - The state container class to connect to
|
|
209
|
+
* @param BlocClass - The state container class to connect to (supports both stateful and stateless)
|
|
187
210
|
* @param options - Configuration options for instance management and lifecycle
|
|
188
211
|
* @returns The state container instance for calling actions
|
|
189
212
|
*
|
|
190
|
-
* @example Basic usage
|
|
213
|
+
* @example Basic usage with stateless container
|
|
214
|
+
* ```ts
|
|
215
|
+
* class AnalyticsService extends StatelessCubit {
|
|
216
|
+
* trackEvent(name: string) { ... }
|
|
217
|
+
* }
|
|
218
|
+
*
|
|
219
|
+
* const analytics = useBlocActions(AnalyticsService);
|
|
220
|
+
* analytics.trackEvent('page_view');
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @example With stateful container (no re-renders)
|
|
191
224
|
* ```ts
|
|
192
225
|
* const myBloc = useBlocActions(MyBloc);
|
|
193
|
-
* //
|
|
194
|
-
* myBloc.someMethod();
|
|
226
|
+
* myBloc.someMethod(); // Won't cause re-renders
|
|
195
227
|
* ```
|
|
196
228
|
*
|
|
197
229
|
* @example With isolated instance
|
|
@@ -200,12 +232,6 @@ function useBloc(BlocClass, options) {
|
|
|
200
232
|
* instanceId: 'unique-id'
|
|
201
233
|
* });
|
|
202
234
|
* ```
|
|
203
|
-
*
|
|
204
|
-
* @example With generic bloc and explicit state type
|
|
205
|
-
* ```ts
|
|
206
|
-
* const myBloc = useBlocActions<MyStateType>(GenericBloc);
|
|
207
|
-
* // myBloc.state is typed as MyStateType
|
|
208
|
-
* ```
|
|
209
235
|
*/
|
|
210
236
|
function useBlocActions(BlocClass, options) {
|
|
211
237
|
const componentRef = useRef({});
|
|
@@ -213,13 +239,13 @@ function useBlocActions(BlocClass, options) {
|
|
|
213
239
|
const [bloc, instanceKey] = useMemo(() => {
|
|
214
240
|
const isIsolated = isIsolatedClass(BlocClass);
|
|
215
241
|
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
216
|
-
return [BlocClass
|
|
242
|
+
return [acquire(BlocClass, instanceKey$1, { props: initialPropsRef.current }), instanceKey$1];
|
|
217
243
|
}, [BlocClass]);
|
|
218
244
|
useEffect(() => {
|
|
219
245
|
if (options?.onMount) options.onMount(bloc);
|
|
220
246
|
return () => {
|
|
221
247
|
if (options?.onUnmount) options.onUnmount(bloc);
|
|
222
|
-
|
|
248
|
+
release(BlocClass, instanceKey);
|
|
223
249
|
if (isIsolatedClass(BlocClass) && !bloc.isDisposed) bloc.dispose();
|
|
224
250
|
};
|
|
225
251
|
}, []);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["defaultConfig: BlacReactConfig","globalConfig: BlacReactConfig","globalConfig","instanceKey","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<T>","adapterState: AdapterState<T>","adapterState","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n ExternalDependencyManager,\n createAutoTrackSubscribe,\n createManualDepsSubscribe,\n createNoTrackSubscribe,\n createAutoTrackSnapshot,\n createManualDepsSnapshot,\n createNoTrackSnapshot,\n initAutoTrackState,\n initManualDepsState,\n initNoTrackState,\n disableGetterTracking,\n isIsolatedClass,\n StateContainerConstructor,\n InstanceState,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * @template S - Optional state type override for generic StateContainers (defaults to never)\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const [state, myBloc] = useBloc<MyStateType>(GenericBloc);\n * // state is typed as MyStateType\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n const componentRef = useRef<ComponentRef>({});\n const isIsolated = isIsolatedClass(BlocClass);\n\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T>,\n TBloc,\n ]\n >(() => {\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = (BlocClass as any).resolve(instanceKey, {\n props: initialPropsRef.current,\n }) as TBloc;\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = initManualDepsState(instance);\n subscribeFn = createManualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = createManualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = initNoTrackState(instance);\n subscribeFn = createNoTrackSubscribe(instance);\n getSnapshotFn = createNoTrackSnapshot(instance);\n } else {\n adapterState = initAutoTrackState(instance);\n subscribeFn = createAutoTrackSubscribe(instance, adapterState);\n getSnapshotFn = createAutoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc!,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDependencyManager());\n\n useEffect(() => {\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance]);\n\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterTracker,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n (BlocClass as any).release(instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n }, []);\n\n return [state, bloc, componentRef];\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport { StateContainerConstructor, isIsolatedClass } from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * @template S - Optional state type override for generic StateContainers (defaults to never)\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options for instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * // Call methods on the bloc without re-rendering\n * myBloc.someMethod();\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const myBloc = useBlocActions<MyStateType>(GenericBloc);\n * // myBloc.state is typed as MyStateType\n * ```\n */\nexport function useBlocActions<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = (BlocClass as any).resolve(instanceKey, {\n props: initialPropsRef.current,\n });\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n (BlocClass as any).release(instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as InstanceType<T>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["defaultConfig: BlacReactConfig","globalConfig: BlacReactConfig","globalConfig","instance","instanceKey","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<T>","adapterState: AdapterState<T>","adapterState","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n type IsStatelessContainer,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n isIsolatedClass,\n isStatelessClass,\n StateContainerConstructor,\n InstanceState,\n acquire,\n release,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * Type that produces a TypeScript error when a stateless container is used.\n * Evaluates to `never` for stateless containers, causing a type mismatch.\n */\ntype StatefulContainer<T extends StateContainerConstructor> =\n IsStatelessContainer<T> extends true ? never : T;\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).\n * Use `useBlocActions()` instead for stateless containers.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (must not be stateless)\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: StatefulContainer<T>,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n const isStateless = isStatelessClass(BlocClass);\n const isIsolated = isIsolatedClass(BlocClass);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T> | null,\n TBloc,\n ]\n >(() => {\n // Runtime check for stateless containers - log error but don't crash\n if (isStateless) {\n console.error(\n `[BlaC] useBloc() does not support stateless containers. ` +\n `\"${BlocClass.name}\" is a StatelessCubit or StatelessVertex. ` +\n `Use useBlocActions() instead for stateless containers.`,\n );\n const instance = acquire(BlocClass, 'default') as TBloc;\n // Return minimal working functions that won't subscribe to anything\n return [\n instance,\n () => () => {},\n () => ({}) as ExtractState<T>,\n undefined,\n null,\n instance,\n ];\n }\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey, {\n props: initialPropsRef.current,\n }) as TBloc;\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isStateless, isIsolated, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n if (isStateless) return;\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance, isStateless]);\n\n useEffect(() => {\n if (isStateless || !adapterState) return;\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (isStateless) return;\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport {\n StateContainerConstructor,\n isIsolatedClass,\n acquire,\n release,\n} from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).\n * It also works with regular Cubit/Vertex when you don't need state subscriptions.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (supports both stateful and stateless)\n * @param options - Configuration options for instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage with stateless container\n * ```ts\n * class AnalyticsService extends StatelessCubit {\n * trackEvent(name: string) { ... }\n * }\n *\n * const analytics = useBlocActions(AnalyticsService);\n * analytics.trackEvent('page_view');\n * ```\n *\n * @example With stateful container (no re-renders)\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * myBloc.someMethod(); // Won't cause re-renders\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBlocActions<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey, {\n props: initialPropsRef.current,\n });\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as InstanceType<T>;\n}\n"],"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.17",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Brendan Mullins <jsnanigans@gmail.com>",
|
|
6
6
|
"repository": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@types/react": "^18.0.0 || ^19.0.0",
|
|
51
51
|
"react": "^18.0.0 || ^19.0.0",
|
|
52
|
-
"@blac/core": "2.0.0-rc.
|
|
52
|
+
"@blac/core": "2.0.0-rc.17"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"@types/react": {
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"tsdown": "^0.15.7",
|
|
79
79
|
"typescript": "^5.9.2",
|
|
80
80
|
"vitest": "3.2.4",
|
|
81
|
-
"@blac/core": "2.0.0-rc.
|
|
81
|
+
"@blac/core": "2.0.0-rc.17"
|
|
82
82
|
},
|
|
83
83
|
"scripts": {
|
|
84
84
|
"dev": "tsdown --watch",
|