@blac/react 2.0.0-rc-7 → 2.0.0-rc-10
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 +63 -14
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +27 -63
- package/dist/index.es.js +88 -157
- package/dist/index.es.js.map +1 -1
- package/package.json +10 -9
- package/src/__tests__/useBloc.tracking.test.tsx +198 -0
- package/src/index.ts +2 -4
- package/src/useBloc.ts +130 -0
- package/src/useExternalBlocStore.ts +116 -191
- package/src/useBloc.tsx +0 -193
|
@@ -1,215 +1,140 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Blac,
|
|
3
|
+
BlocBase,
|
|
4
|
+
BlocConstructor,
|
|
5
|
+
BlocState,
|
|
6
|
+
InferPropsFromGeneric,
|
|
7
|
+
generateUUID,
|
|
8
|
+
} from '@blac/core';
|
|
2
9
|
import { useCallback, useMemo, useRef } from 'react';
|
|
3
|
-
import { BlocHookOptions } from './useBloc';
|
|
4
10
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Gets the current snapshot of the store state.
|
|
17
|
-
* @returns The current state of the store
|
|
18
|
-
*/
|
|
19
|
-
getSnapshot: () => BlocState<InstanceType<B>> | undefined;
|
|
11
|
+
interface ExternalStoreOptions<B extends BlocBase<any>> {
|
|
12
|
+
id?: string;
|
|
13
|
+
props?: InferPropsFromGeneric<B>;
|
|
14
|
+
selector?: (
|
|
15
|
+
currentState: BlocState<B>,
|
|
16
|
+
previousState: BlocState<B>,
|
|
17
|
+
instance: B,
|
|
18
|
+
) => any[];
|
|
19
|
+
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*/
|
|
26
|
-
getServerSnapshot?: () => BlocState<InstanceType<B>> | undefined;
|
|
21
|
+
interface ExternalStore<T> {
|
|
22
|
+
getSnapshot: () => T | undefined;
|
|
23
|
+
subscribe: (listener: () => void) => () => void;
|
|
24
|
+
getServerSnapshot?: () => T | undefined;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
usedKeys:
|
|
33
|
-
usedClassPropKeys:
|
|
34
|
-
externalStore: ExternalStore<B>;
|
|
35
|
-
instance: React.RefObject<InstanceType<B>>;
|
|
27
|
+
interface ExternalBlocStoreResult<B extends BlocBase<any>> {
|
|
28
|
+
externalStore: ExternalStore<BlocState<B>>;
|
|
29
|
+
instance: { current: B | null };
|
|
30
|
+
usedKeys: { current: Set<string> };
|
|
31
|
+
usedClassPropKeys: { current: Set<string> };
|
|
36
32
|
rid: string;
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
36
|
+
* Hook for using an external bloc store
|
|
37
|
+
* Provides low-level access to bloc instances for use with React's useSyncExternalStore
|
|
42
38
|
*/
|
|
43
|
-
|
|
44
|
-
B extends BlocConstructor<BlocBase<any
|
|
39
|
+
export default function useExternalBlocStore<
|
|
40
|
+
B extends BlocConstructor<BlocBase<any>>,
|
|
45
41
|
>(
|
|
46
|
-
|
|
47
|
-
options:
|
|
48
|
-
):
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
42
|
+
blocConstructor: B,
|
|
43
|
+
options: ExternalStoreOptions<InstanceType<B>> = {},
|
|
44
|
+
): ExternalBlocStoreResult<InstanceType<B>> {
|
|
45
|
+
const ridRef = useRef<string>(`external-${generateUUID()}`);
|
|
46
|
+
const usedKeysRef = useRef<Set<string>>(new Set());
|
|
47
|
+
const usedClassPropKeysRef = useRef<Set<string>>(new Set());
|
|
48
|
+
const instanceRef = useRef<InstanceType<B> | null>(null);
|
|
49
|
+
|
|
50
|
+
// Get or create bloc instance
|
|
51
|
+
const bloc = useMemo(() => {
|
|
52
|
+
const blac = Blac.getInstance();
|
|
53
|
+
const base = blocConstructor as unknown as BlocBase<any>;
|
|
54
|
+
|
|
55
|
+
// For isolated blocs, always create a new instance
|
|
56
|
+
if (
|
|
57
|
+
(base.constructor as any).isolated ||
|
|
58
|
+
(blocConstructor as any).isolated
|
|
59
|
+
) {
|
|
60
|
+
const newBloc = new blocConstructor(options?.props) as InstanceType<B>;
|
|
61
|
+
const uniqueId =
|
|
62
|
+
options?.id || `${blocConstructor.name}_${generateUUID()}`;
|
|
63
|
+
newBloc._updateId(uniqueId);
|
|
64
|
+
blac.activateBloc(newBloc);
|
|
65
|
+
return newBloc;
|
|
66
|
+
}
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
return
|
|
65
|
-
id:
|
|
66
|
-
props,
|
|
67
|
-
instanceRef: rid
|
|
68
|
+
// For shared blocs, use the existing getBloc logic
|
|
69
|
+
return blac.getBloc(blocConstructor, {
|
|
70
|
+
id: options?.id,
|
|
71
|
+
props: options?.props,
|
|
68
72
|
});
|
|
69
|
-
}, [
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
() =>
|
|
81
|
-
(newState): ReturnType<BlocHookDependencyArrayFn<BlocState<InstanceType<B>>>> => {
|
|
82
|
-
const instance = blocInstance.current;
|
|
83
|
-
|
|
84
|
-
if (!instance) {
|
|
85
|
-
return [];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Use custom dependency selector if provided
|
|
89
|
-
if (selector) {
|
|
90
|
-
return selector(newState);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Fall back to bloc's default dependency selector if available
|
|
94
|
-
if (instance.defaultDependencySelector) {
|
|
95
|
-
return instance.defaultDependencySelector(newState);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// For primitive states, use default selector
|
|
99
|
-
if (typeof newState !== 'object') {
|
|
100
|
-
// Default behavior for primitive states: re-render if the state itself changes.
|
|
101
|
-
return [[newState]];
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// For object states, track which properties were actually used
|
|
105
|
-
const usedStateValues: unknown[] = [];
|
|
106
|
-
for (const key of usedKeys.current) {
|
|
107
|
-
if (key in newState) {
|
|
108
|
-
usedStateValues.push(newState[key as keyof typeof newState]);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Track used class properties for dependency tracking, this enables rerenders when class getters change
|
|
113
|
-
const usedClassValues: unknown[] = [];
|
|
114
|
-
for (const key of usedClassPropKeys.current) {
|
|
115
|
-
if (key in instance) {
|
|
116
|
-
try {
|
|
117
|
-
const value = instance[key as keyof InstanceType<B>];
|
|
118
|
-
switch (typeof value) {
|
|
119
|
-
case 'function':
|
|
120
|
-
continue;
|
|
121
|
-
default:
|
|
122
|
-
usedClassValues.push(value);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
} catch (error) {
|
|
126
|
-
Blac.instance.log('useBloc Error', error);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// If no state properties have been accessed, return empty dependencies to prevent re-renders
|
|
132
|
-
// Class properties can change independently of state
|
|
133
|
-
if (usedKeys.current.size === 0) {
|
|
134
|
-
return usedClassPropKeys.current.size > 0 ? [usedClassValues] : [[]];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return [usedStateValues, usedClassValues];
|
|
138
|
-
},
|
|
139
|
-
[],
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
const state: ExternalStore<B> = useMemo(() => {
|
|
143
|
-
return {
|
|
144
|
-
subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {
|
|
145
|
-
const currentInstance = blocInstance.current;
|
|
73
|
+
}, [blocConstructor, options?.id]);
|
|
74
|
+
|
|
75
|
+
// Create external store interface
|
|
76
|
+
const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(
|
|
77
|
+
() => ({
|
|
78
|
+
getSnapshot: () => {
|
|
79
|
+
const currentInstance = instanceRef.current;
|
|
80
|
+
return currentInstance ? currentInstance.state : undefined;
|
|
81
|
+
},
|
|
82
|
+
subscribe: (listener: () => void) => {
|
|
83
|
+
const currentInstance = instanceRef.current;
|
|
146
84
|
if (!currentInstance) {
|
|
147
|
-
|
|
85
|
+
// Return no-op unsubscribe if no instance
|
|
86
|
+
return () => {};
|
|
148
87
|
}
|
|
149
88
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
console.error({
|
|
164
|
-
e,
|
|
165
|
-
blocInstance: currentInstance,
|
|
166
|
-
dependencyArray,
|
|
167
|
-
});
|
|
89
|
+
// Wrap listener to handle errors gracefully
|
|
90
|
+
const safeListener = (
|
|
91
|
+
newState: BlocState<InstanceType<B>>,
|
|
92
|
+
oldState: BlocState<InstanceType<B>>,
|
|
93
|
+
) => {
|
|
94
|
+
try {
|
|
95
|
+
// Reset tracking keys on each listener call
|
|
96
|
+
usedKeysRef.current.clear();
|
|
97
|
+
usedClassPropKeysRef.current.clear();
|
|
98
|
+
|
|
99
|
+
// Call selector if provided
|
|
100
|
+
if (options.selector && currentInstance) {
|
|
101
|
+
options.selector(newState, oldState, currentInstance);
|
|
168
102
|
}
|
|
169
|
-
},
|
|
170
|
-
// Pass the dependency array to control when the subscription is updated
|
|
171
|
-
dependencyArray,
|
|
172
|
-
// Use the provided id to identify this subscription
|
|
173
|
-
id: rid,
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
Blac.activateBloc(currentInstance);
|
|
177
|
-
|
|
178
|
-
// Subscribe to the bloc's observer with the provided listener function
|
|
179
|
-
// This will trigger the callback whenever the bloc's state changes
|
|
180
|
-
const unSub = currentInstance._observer.subscribe(observer);
|
|
181
103
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
104
|
+
// Call listener with state if it expects it
|
|
105
|
+
if (listener.length > 0) {
|
|
106
|
+
(listener as any)(newState);
|
|
107
|
+
} else {
|
|
108
|
+
listener();
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('Listener error in useExternalBlocStore:', error);
|
|
112
|
+
// Don't rethrow to prevent breaking other listeners
|
|
113
|
+
}
|
|
185
114
|
};
|
|
115
|
+
|
|
116
|
+
const unsubscribe = currentInstance._observer.subscribe({
|
|
117
|
+
id: ridRef.current,
|
|
118
|
+
fn: safeListener,
|
|
119
|
+
});
|
|
120
|
+
return unsubscribe;
|
|
186
121
|
},
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (!instance) {
|
|
191
|
-
return {} as BlocState<InstanceType<B>>;
|
|
192
|
-
}
|
|
193
|
-
return instance.state;
|
|
194
|
-
},
|
|
195
|
-
// Server snapshot mirrors the client snapshot in this implementation
|
|
196
|
-
getServerSnapshot: (): BlocState<InstanceType<B>> | undefined => {
|
|
197
|
-
const instance = blocInstance.current;
|
|
198
|
-
if (!instance) {
|
|
199
|
-
return {} as BlocState<InstanceType<B>>;
|
|
200
|
-
}
|
|
201
|
-
return instance.state;
|
|
122
|
+
getServerSnapshot: () => {
|
|
123
|
+
const currentInstance = instanceRef.current;
|
|
124
|
+
return currentInstance ? currentInstance.state : undefined;
|
|
202
125
|
},
|
|
203
|
-
}
|
|
204
|
-
|
|
126
|
+
}),
|
|
127
|
+
[],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Update instance ref
|
|
131
|
+
instanceRef.current = bloc;
|
|
205
132
|
|
|
206
133
|
return {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
rid,
|
|
134
|
+
externalStore,
|
|
135
|
+
instance: instanceRef,
|
|
136
|
+
usedKeys: usedKeysRef,
|
|
137
|
+
usedClassPropKeys: usedClassPropKeysRef,
|
|
138
|
+
rid: ridRef.current,
|
|
212
139
|
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export default useExternalBlocStore;
|
|
140
|
+
}
|
package/src/useBloc.tsx
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BlocBase,
|
|
3
|
-
BlocConstructor,
|
|
4
|
-
BlocHookDependencyArrayFn,
|
|
5
|
-
BlocState,
|
|
6
|
-
InferPropsFromGeneric,
|
|
7
|
-
} from '@blac/core';
|
|
8
|
-
import {
|
|
9
|
-
useEffect,
|
|
10
|
-
useMemo,
|
|
11
|
-
useRef,
|
|
12
|
-
useSyncExternalStore
|
|
13
|
-
} from 'react';
|
|
14
|
-
import useExternalBlocStore from './useExternalBlocStore';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Type definition for the return type of the useBloc hook
|
|
18
|
-
* @template B - Bloc constructor type
|
|
19
|
-
*/
|
|
20
|
-
type HookTypes<B extends BlocConstructor<BlocBase<any>>> = [
|
|
21
|
-
BlocState<InstanceType<B>>,
|
|
22
|
-
InstanceType<B>,
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Configuration options for the useBloc hook
|
|
27
|
-
*/
|
|
28
|
-
export interface BlocHookOptions<B extends BlocBase<any>> {
|
|
29
|
-
id?: string;
|
|
30
|
-
selector?: BlocHookDependencyArrayFn<BlocState<B>>;
|
|
31
|
-
props?: InferPropsFromGeneric<B>;
|
|
32
|
-
onMount?: (bloc: B) => void;
|
|
33
|
-
onUnmount?: (bloc: B) => void;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Default dependency selector that wraps the entire state in an array
|
|
38
|
-
* @template T - State type
|
|
39
|
-
* @returns {Array<Array<T>>} Dependency array containing the entire state
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* React hook for integrating with Blac state management
|
|
44
|
-
*
|
|
45
|
-
* This hook connects a React component to a Bloc state container, providing
|
|
46
|
-
* automatic re-rendering when relevant state changes, dependency tracking,
|
|
47
|
-
* and proper lifecycle management.
|
|
48
|
-
*
|
|
49
|
-
* @template B - Bloc constructor type
|
|
50
|
-
* @template O - BlocHookOptions type
|
|
51
|
-
* @param {B} bloc - Bloc constructor class
|
|
52
|
-
* @param {O} [options] - Configuration options for the hook
|
|
53
|
-
* @returns {HookTypes<B>} Tuple containing [state, bloc instance]
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* const [state, counterBloc] = useBloc(CounterBloc);
|
|
57
|
-
* // Access state
|
|
58
|
-
* console.log(state.count);
|
|
59
|
-
* // Call bloc methods
|
|
60
|
-
* counterBloc.increment();
|
|
61
|
-
*/
|
|
62
|
-
export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
|
|
63
|
-
bloc: B,
|
|
64
|
-
options?: BlocHookOptions<InstanceType<B>>,
|
|
65
|
-
): HookTypes<B> {
|
|
66
|
-
// Determine ID for isolated or shared blocs
|
|
67
|
-
// const base = bloc as unknown as BlocBaseAbstract;
|
|
68
|
-
// const isIsolated = base.isolated;
|
|
69
|
-
// const effectiveBlocId = isIsolated ? rid : blocId;
|
|
70
|
-
const {
|
|
71
|
-
externalStore,
|
|
72
|
-
usedKeys,
|
|
73
|
-
usedClassPropKeys,
|
|
74
|
-
instance,
|
|
75
|
-
rid,
|
|
76
|
-
} = useExternalBlocStore(bloc, options);
|
|
77
|
-
|
|
78
|
-
// Subscribe to state changes using React's external store API
|
|
79
|
-
const state = useSyncExternalStore<BlocState<InstanceType<B>>>(
|
|
80
|
-
externalStore.subscribe,
|
|
81
|
-
() => {
|
|
82
|
-
const snapshot = externalStore.getSnapshot();
|
|
83
|
-
if (snapshot === undefined) {
|
|
84
|
-
throw new Error(`[useBloc] State snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`);
|
|
85
|
-
}
|
|
86
|
-
return snapshot;
|
|
87
|
-
},
|
|
88
|
-
externalStore.getServerSnapshot ? () => {
|
|
89
|
-
const serverSnapshot = externalStore.getServerSnapshot!();
|
|
90
|
-
if (serverSnapshot === undefined) {
|
|
91
|
-
throw new Error(`[useBloc] Server state snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`);
|
|
92
|
-
}
|
|
93
|
-
return serverSnapshot;
|
|
94
|
-
} : undefined,
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
// Cache proxies to avoid recreation on every render
|
|
98
|
-
const stateProxyCache = useRef<WeakMap<object, object>>(new WeakMap());
|
|
99
|
-
const classProxyCache = useRef<WeakMap<object, object>>(new WeakMap());
|
|
100
|
-
|
|
101
|
-
const returnState = useMemo(() => {
|
|
102
|
-
if (typeof state !== 'object' || state === null) {
|
|
103
|
-
return state;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Check cache first
|
|
107
|
-
let proxy = stateProxyCache.current.get(state);
|
|
108
|
-
if (!proxy) {
|
|
109
|
-
proxy = new Proxy(state, {
|
|
110
|
-
get(target, prop) {
|
|
111
|
-
// Use setTimeout to defer dependency tracking until after render
|
|
112
|
-
setTimeout(() => {
|
|
113
|
-
usedKeys.current.add(prop as string);
|
|
114
|
-
}, 0);
|
|
115
|
-
const value = target[prop as keyof typeof target];
|
|
116
|
-
return value;
|
|
117
|
-
},
|
|
118
|
-
// Handle symbols and non-enumerable properties
|
|
119
|
-
has(target, prop) {
|
|
120
|
-
return prop in target;
|
|
121
|
-
},
|
|
122
|
-
ownKeys(target) {
|
|
123
|
-
return Reflect.ownKeys(target);
|
|
124
|
-
},
|
|
125
|
-
getOwnPropertyDescriptor(target, prop) {
|
|
126
|
-
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
stateProxyCache.current.set(state as object, proxy as object);
|
|
130
|
-
}
|
|
131
|
-
return proxy;
|
|
132
|
-
}, [state]);
|
|
133
|
-
|
|
134
|
-
const returnClass = useMemo(() => {
|
|
135
|
-
if (!instance.current) {
|
|
136
|
-
throw new Error(`[useBloc] Bloc instance is null for ${bloc.name}. This should never happen - bloc instance must be defined.`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Check cache first
|
|
140
|
-
let proxy = classProxyCache.current.get(instance.current);
|
|
141
|
-
if (!proxy) {
|
|
142
|
-
proxy = new Proxy(instance.current, {
|
|
143
|
-
get(target, prop) {
|
|
144
|
-
if (!target) {
|
|
145
|
-
throw new Error(`[useBloc] Bloc target is null for ${bloc.name}. This should never happen - bloc target must be defined.`);
|
|
146
|
-
}
|
|
147
|
-
const value = target[prop as keyof InstanceType<B>];
|
|
148
|
-
if (typeof value !== 'function') {
|
|
149
|
-
usedClassPropKeys.current.add(prop as string);
|
|
150
|
-
}
|
|
151
|
-
return value;
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
classProxyCache.current.set(instance.current, proxy);
|
|
155
|
-
}
|
|
156
|
-
return proxy;
|
|
157
|
-
}, [instance.current?.uid]);
|
|
158
|
-
|
|
159
|
-
// Create a stable reference object for this component
|
|
160
|
-
const componentRef = useRef({});
|
|
161
|
-
|
|
162
|
-
// Set up bloc lifecycle management
|
|
163
|
-
useEffect(() => {
|
|
164
|
-
const currentInstance = instance.current;
|
|
165
|
-
if (!currentInstance) return;
|
|
166
|
-
|
|
167
|
-
// Pass component reference for proper memory management
|
|
168
|
-
currentInstance._addConsumer(rid, componentRef.current);
|
|
169
|
-
|
|
170
|
-
// Call onMount callback if provided
|
|
171
|
-
options?.onMount?.(currentInstance);
|
|
172
|
-
|
|
173
|
-
// Cleanup: remove this component as a consumer using the captured instance
|
|
174
|
-
return () => {
|
|
175
|
-
if (!currentInstance) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
options?.onUnmount?.(currentInstance);
|
|
179
|
-
currentInstance._removeConsumer(rid);
|
|
180
|
-
};
|
|
181
|
-
}, [instance.current?.uid, rid]); // Use UID to ensure we re-run when instance changes
|
|
182
|
-
|
|
183
|
-
// Ensure state and instance are never undefined/null
|
|
184
|
-
if (returnState === undefined) {
|
|
185
|
-
throw new Error(`[useBloc] State is undefined for ${bloc.name}. This should never happen - state must be defined.`);
|
|
186
|
-
}
|
|
187
|
-
if (!returnClass) {
|
|
188
|
-
throw new Error(`[useBloc] Instance is null for ${bloc.name}. This should never happen - instance must be defined.`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Safe return with proper typing
|
|
192
|
-
return [returnState, returnClass] as [BlocState<InstanceType<B>>, InstanceType<B>];
|
|
193
|
-
}
|