@blac/react 2.0.0-rc-7 → 2.0.0-rc-8
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 +1 -0
- package/dist/index.es.js +347 -127
- package/dist/index.es.js.map +1 -1
- package/package.json +2 -2
- package/src/DependencyTracker.ts +350 -0
- package/src/useBloc.tsx +69 -44
- package/src/useExternalBlocStore.ts +145 -45
package/src/useBloc.tsx
CHANGED
|
@@ -5,13 +5,9 @@ import {
|
|
|
5
5
|
BlocState,
|
|
6
6
|
InferPropsFromGeneric,
|
|
7
7
|
} from '@blac/core';
|
|
8
|
-
import {
|
|
9
|
-
useEffect,
|
|
10
|
-
useMemo,
|
|
11
|
-
useRef,
|
|
12
|
-
useSyncExternalStore
|
|
13
|
-
} from 'react';
|
|
8
|
+
import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
14
9
|
import useExternalBlocStore from './useExternalBlocStore';
|
|
10
|
+
import { DependencyTracker } from './DependencyTracker';
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
13
|
* Type definition for the return type of the useBloc hook
|
|
@@ -63,59 +59,74 @@ export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
|
|
|
63
59
|
bloc: B,
|
|
64
60
|
options?: BlocHookOptions<InstanceType<B>>,
|
|
65
61
|
): 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
62
|
const {
|
|
71
63
|
externalStore,
|
|
72
64
|
usedKeys,
|
|
73
65
|
usedClassPropKeys,
|
|
74
66
|
instance,
|
|
75
67
|
rid,
|
|
68
|
+
hasProxyTracking,
|
|
76
69
|
} = useExternalBlocStore(bloc, options);
|
|
77
70
|
|
|
78
|
-
// Subscribe to state changes using React's external store API
|
|
79
71
|
const state = useSyncExternalStore<BlocState<InstanceType<B>>>(
|
|
80
72
|
externalStore.subscribe,
|
|
81
73
|
() => {
|
|
82
74
|
const snapshot = externalStore.getSnapshot();
|
|
83
75
|
if (snapshot === undefined) {
|
|
84
|
-
throw new Error(
|
|
76
|
+
throw new Error(
|
|
77
|
+
`[useBloc] State snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`,
|
|
78
|
+
);
|
|
85
79
|
}
|
|
86
80
|
return snapshot;
|
|
87
81
|
},
|
|
88
|
-
externalStore.getServerSnapshot
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
82
|
+
externalStore.getServerSnapshot
|
|
83
|
+
? () => {
|
|
84
|
+
const serverSnapshot = externalStore.getServerSnapshot!();
|
|
85
|
+
if (serverSnapshot === undefined) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`[useBloc] Server state snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return serverSnapshot;
|
|
91
|
+
}
|
|
92
|
+
: undefined,
|
|
95
93
|
);
|
|
96
94
|
|
|
97
|
-
|
|
95
|
+
const dependencyTracker = useRef<DependencyTracker | null>(null);
|
|
96
|
+
if (!dependencyTracker.current) {
|
|
97
|
+
dependencyTracker.current = new DependencyTracker({
|
|
98
|
+
enableBatching: true,
|
|
99
|
+
enableMetrics: process.env.NODE_ENV === 'development',
|
|
100
|
+
enableDeepTracking: false,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
98
104
|
const stateProxyCache = useRef<WeakMap<object, object>>(new WeakMap());
|
|
99
105
|
const classProxyCache = useRef<WeakMap<object, object>>(new WeakMap());
|
|
100
106
|
|
|
101
107
|
const returnState = useMemo(() => {
|
|
108
|
+
// If a custom selector is provided, don't use proxy tracking
|
|
109
|
+
if (options?.selector) {
|
|
110
|
+
return state;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
hasProxyTracking.current = true;
|
|
114
|
+
|
|
102
115
|
if (typeof state !== 'object' || state === null) {
|
|
103
116
|
return state;
|
|
104
117
|
}
|
|
105
118
|
|
|
106
|
-
// Check cache first
|
|
107
119
|
let proxy = stateProxyCache.current.get(state);
|
|
108
120
|
if (!proxy) {
|
|
109
121
|
proxy = new Proxy(state, {
|
|
110
122
|
get(target, prop) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
123
|
+
if (typeof prop === 'string') {
|
|
124
|
+
usedKeys.current.add(prop);
|
|
125
|
+
dependencyTracker.current?.trackStateAccess(prop);
|
|
126
|
+
}
|
|
115
127
|
const value = target[prop as keyof typeof target];
|
|
116
128
|
return value;
|
|
117
129
|
},
|
|
118
|
-
// Handle symbols and non-enumerable properties
|
|
119
130
|
has(target, prop) {
|
|
120
131
|
return prop in target;
|
|
121
132
|
},
|
|
@@ -124,7 +135,7 @@ export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
|
|
|
124
135
|
},
|
|
125
136
|
getOwnPropertyDescriptor(target, prop) {
|
|
126
137
|
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
127
|
-
}
|
|
138
|
+
},
|
|
128
139
|
});
|
|
129
140
|
stateProxyCache.current.set(state as object, proxy as object);
|
|
130
141
|
}
|
|
@@ -133,20 +144,24 @@ export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
|
|
|
133
144
|
|
|
134
145
|
const returnClass = useMemo(() => {
|
|
135
146
|
if (!instance.current) {
|
|
136
|
-
throw new Error(
|
|
147
|
+
throw new Error(
|
|
148
|
+
`[useBloc] Bloc instance is null for ${bloc.name}. This should never happen - bloc instance must be defined.`,
|
|
149
|
+
);
|
|
137
150
|
}
|
|
138
151
|
|
|
139
|
-
// Check cache first
|
|
140
152
|
let proxy = classProxyCache.current.get(instance.current);
|
|
141
153
|
if (!proxy) {
|
|
142
154
|
proxy = new Proxy(instance.current, {
|
|
143
155
|
get(target, prop) {
|
|
144
156
|
if (!target) {
|
|
145
|
-
throw new Error(
|
|
157
|
+
throw new Error(
|
|
158
|
+
`[useBloc] Bloc target is null for ${bloc.name}. This should never happen - bloc target must be defined.`,
|
|
159
|
+
);
|
|
146
160
|
}
|
|
147
161
|
const value = target[prop as keyof InstanceType<B>];
|
|
148
|
-
if (typeof value !== 'function') {
|
|
149
|
-
usedClassPropKeys.current.add(prop
|
|
162
|
+
if (typeof value !== 'function' && typeof prop === 'string') {
|
|
163
|
+
usedClassPropKeys.current.add(prop);
|
|
164
|
+
dependencyTracker.current?.trackClassAccess(prop);
|
|
150
165
|
}
|
|
151
166
|
return value;
|
|
152
167
|
},
|
|
@@ -156,38 +171,48 @@ export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
|
|
|
156
171
|
return proxy;
|
|
157
172
|
}, [instance.current?.uid]);
|
|
158
173
|
|
|
159
|
-
// Create a stable reference object for this component
|
|
160
174
|
const componentRef = useRef({});
|
|
161
175
|
|
|
162
|
-
// Set up bloc lifecycle management
|
|
163
176
|
useEffect(() => {
|
|
164
177
|
const currentInstance = instance.current;
|
|
165
178
|
if (!currentInstance) return;
|
|
166
|
-
|
|
167
|
-
// Pass component reference for proper memory management
|
|
179
|
+
|
|
168
180
|
currentInstance._addConsumer(rid, componentRef.current);
|
|
169
181
|
|
|
170
|
-
// Call onMount callback if provided
|
|
171
182
|
options?.onMount?.(currentInstance);
|
|
172
183
|
|
|
173
|
-
// Cleanup: remove this component as a consumer using the captured instance
|
|
174
184
|
return () => {
|
|
175
185
|
if (!currentInstance) {
|
|
176
186
|
return;
|
|
177
187
|
}
|
|
178
188
|
options?.onUnmount?.(currentInstance);
|
|
179
189
|
currentInstance._removeConsumer(rid);
|
|
190
|
+
|
|
191
|
+
dependencyTracker.current?.reset();
|
|
180
192
|
};
|
|
181
|
-
}, [instance.current?.uid, rid]);
|
|
193
|
+
}, [instance.current?.uid, rid]);
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (process.env.NODE_ENV === 'development' && dependencyTracker.current) {
|
|
196
|
+
const metrics = dependencyTracker.current.getMetrics();
|
|
197
|
+
if (metrics.stateAccessCount > 0 || metrics.classAccessCount > 0) {
|
|
198
|
+
console.debug(`[useBloc] ${bloc.name} Performance Metrics:`, metrics);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
182
202
|
|
|
183
|
-
// Ensure state and instance are never undefined/null
|
|
184
203
|
if (returnState === undefined) {
|
|
185
|
-
throw new Error(
|
|
204
|
+
throw new Error(
|
|
205
|
+
`[useBloc] State is undefined for ${bloc.name}. This should never happen - state must be defined.`,
|
|
206
|
+
);
|
|
186
207
|
}
|
|
187
208
|
if (!returnClass) {
|
|
188
|
-
throw new Error(
|
|
209
|
+
throw new Error(
|
|
210
|
+
`[useBloc] Instance is null for ${bloc.name}. This should never happen - instance must be defined.`,
|
|
211
|
+
);
|
|
189
212
|
}
|
|
190
213
|
|
|
191
|
-
|
|
192
|
-
|
|
214
|
+
return [returnState, returnClass] as [
|
|
215
|
+
BlocState<InstanceType<B>>,
|
|
216
|
+
InstanceType<B>,
|
|
217
|
+
];
|
|
193
218
|
}
|
|
@@ -34,6 +34,7 @@ export interface ExternalBlacStore<
|
|
|
34
34
|
externalStore: ExternalStore<B>;
|
|
35
35
|
instance: React.RefObject<InstanceType<B>>;
|
|
36
36
|
rid: string;
|
|
37
|
+
hasProxyTracking: React.RefObject<boolean>;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -59,6 +60,10 @@ const useExternalBlocStore = <
|
|
|
59
60
|
|
|
60
61
|
const usedKeys = useRef<Set<string>>(new Set());
|
|
61
62
|
const usedClassPropKeys = useRef<Set<string>>(new Set());
|
|
63
|
+
|
|
64
|
+
// Track whether proxy-based dependency tracking has been initialized
|
|
65
|
+
// This helps distinguish between direct external store usage and useBloc proxy usage
|
|
66
|
+
const hasProxyTracking = useRef<boolean>(false);
|
|
62
67
|
|
|
63
68
|
const getBloc = useCallback(() => {
|
|
64
69
|
return Blac.getBloc(bloc, {
|
|
@@ -75,52 +80,67 @@ const useExternalBlocStore = <
|
|
|
75
80
|
blocInstance.current = getBloc();
|
|
76
81
|
}, [getBloc]);
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
// Track previous state and dependencies for selector
|
|
84
|
+
const previousStateRef = useRef<BlocState<InstanceType<B>> | undefined>(undefined);
|
|
85
|
+
const lastDependenciesRef = useRef<unknown[][] | undefined>(undefined);
|
|
86
|
+
const lastStableSnapshot = useRef<BlocState<InstanceType<B>> | undefined>(undefined);
|
|
87
|
+
|
|
88
|
+
// Track bloc instance uid to prevent unnecessary store recreation
|
|
89
|
+
const blocUidRef = useRef<string | undefined>(undefined);
|
|
90
|
+
if (blocUidRef.current !== blocInstance.current?.uid) {
|
|
91
|
+
blocUidRef.current = blocInstance.current?.uid;
|
|
92
|
+
}
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// Use custom dependency selector if provided
|
|
89
|
-
if (selector) {
|
|
90
|
-
return selector(newState);
|
|
91
|
-
}
|
|
94
|
+
const dependencyArray = useMemo(
|
|
95
|
+
() =>
|
|
96
|
+
(newState: BlocState<InstanceType<B>>, oldState?: BlocState<InstanceType<B>>): unknown[][] => {
|
|
97
|
+
const instance = blocInstance.current;
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
99
|
+
if (!instance) {
|
|
100
|
+
return [[], []]; // [stateArray, classArray]
|
|
101
|
+
}
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
+
// Use the provided oldState or fall back to our tracked previous state
|
|
104
|
+
const previousState = oldState ?? previousStateRef.current;
|
|
105
|
+
|
|
106
|
+
let currentDependencies: unknown[][];
|
|
103
107
|
|
|
108
|
+
// Use custom dependency selector if provided
|
|
109
|
+
if (selector) {
|
|
110
|
+
const flatDeps = selector(newState, previousState, instance);
|
|
111
|
+
// Wrap flat custom selector result in the two-array structure for consistency
|
|
112
|
+
currentDependencies = [flatDeps, []]; // [customSelectorDeps, classArray]
|
|
113
|
+
}
|
|
114
|
+
// Fall back to bloc's default dependency selector if available
|
|
115
|
+
else if (instance.defaultDependencySelector) {
|
|
116
|
+
const flatDeps = instance.defaultDependencySelector(newState, previousState, instance);
|
|
117
|
+
// Wrap flat default selector result in the two-array structure for consistency
|
|
118
|
+
currentDependencies = [flatDeps, []]; // [defaultSelectorDeps, classArray]
|
|
119
|
+
}
|
|
120
|
+
// For primitive states, use default selector
|
|
121
|
+
else if (typeof newState !== 'object') {
|
|
122
|
+
// Default behavior for primitive states: re-render if the state itself changes.
|
|
123
|
+
currentDependencies = [[newState], []]; // [primitiveStateArray, classArray]
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
104
126
|
// For object states, track which properties were actually used
|
|
105
|
-
const
|
|
127
|
+
const stateDependencies: unknown[] = [];
|
|
128
|
+
const classDependencies: unknown[] = [];
|
|
129
|
+
|
|
130
|
+
// Add state property values that were accessed
|
|
106
131
|
for (const key of usedKeys.current) {
|
|
107
132
|
if (key in newState) {
|
|
108
|
-
|
|
133
|
+
stateDependencies.push(newState[key as keyof typeof newState]);
|
|
109
134
|
}
|
|
110
135
|
}
|
|
111
136
|
|
|
112
|
-
//
|
|
113
|
-
const usedClassValues: unknown[] = [];
|
|
137
|
+
// Add class property values that were accessed
|
|
114
138
|
for (const key of usedClassPropKeys.current) {
|
|
115
139
|
if (key in instance) {
|
|
116
140
|
try {
|
|
117
141
|
const value = instance[key as keyof InstanceType<B>];
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
continue;
|
|
121
|
-
default:
|
|
122
|
-
usedClassValues.push(value);
|
|
123
|
-
continue;
|
|
142
|
+
if (typeof value !== 'function') {
|
|
143
|
+
classDependencies.push(value);
|
|
124
144
|
}
|
|
125
145
|
} catch (error) {
|
|
126
146
|
Blac.instance.log('useBloc Error', error);
|
|
@@ -128,32 +148,57 @@ const useExternalBlocStore = <
|
|
|
128
148
|
}
|
|
129
149
|
}
|
|
130
150
|
|
|
131
|
-
// If no
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
151
|
+
// If no properties have been accessed through proxy
|
|
152
|
+
if (usedKeys.current.size === 0 && usedClassPropKeys.current.size === 0) {
|
|
153
|
+
// If proxy tracking has never been initialized, this is direct external store usage
|
|
154
|
+
// In this case, always track the entire state to ensure notifications
|
|
155
|
+
if (!hasProxyTracking.current) {
|
|
156
|
+
stateDependencies.push(newState);
|
|
157
|
+
}
|
|
158
|
+
// If proxy tracking was initialized but no properties accessed,
|
|
159
|
+
// return empty dependencies to prevent unnecessary re-renders
|
|
135
160
|
}
|
|
136
161
|
|
|
137
|
-
|
|
162
|
+
currentDependencies = [stateDependencies, classDependencies];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Update tracked state
|
|
166
|
+
previousStateRef.current = newState;
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
// Return the dependencies for BlacObserver to compare
|
|
170
|
+
return currentDependencies;
|
|
138
171
|
},
|
|
139
172
|
[],
|
|
140
173
|
);
|
|
141
174
|
|
|
175
|
+
// Store active subscriptions to reuse observers
|
|
176
|
+
const activeObservers = useRef<Map<Function, { observer: BlacObserver<BlocState<InstanceType<B>>>, unsubscribe: () => void }>>(new Map());
|
|
177
|
+
|
|
142
178
|
const state: ExternalStore<B> = useMemo(() => {
|
|
143
179
|
return {
|
|
144
180
|
subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {
|
|
181
|
+
|
|
145
182
|
const currentInstance = blocInstance.current;
|
|
146
183
|
if (!currentInstance) {
|
|
147
184
|
return () => {}; // Return no-op if no instance
|
|
148
185
|
}
|
|
149
186
|
|
|
187
|
+
// Check if we already have an observer for this listener
|
|
188
|
+
const existing = activeObservers.current.get(listener);
|
|
189
|
+
if (existing) {
|
|
190
|
+
return existing.unsubscribe;
|
|
191
|
+
}
|
|
150
192
|
const observer: BlacObserver<BlocState<InstanceType<B>>> = {
|
|
151
193
|
fn: () => {
|
|
152
194
|
try {
|
|
153
|
-
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
195
|
+
|
|
196
|
+
// Only reset dependency tracking if we're not using a custom selector
|
|
197
|
+
// Custom selectors override proxy-based tracking entirely
|
|
198
|
+
if (!selector && !currentInstance.defaultDependencySelector) {
|
|
199
|
+
usedKeys.current = new Set();
|
|
200
|
+
usedClassPropKeys.current = new Set();
|
|
201
|
+
}
|
|
157
202
|
|
|
158
203
|
// Only trigger listener if there are actual subscriptions
|
|
159
204
|
listener(currentInstance.state);
|
|
@@ -179,10 +224,16 @@ const useExternalBlocStore = <
|
|
|
179
224
|
// This will trigger the callback whenever the bloc's state changes
|
|
180
225
|
const unSub = currentInstance._observer.subscribe(observer);
|
|
181
226
|
|
|
182
|
-
|
|
183
|
-
|
|
227
|
+
const unsubscribe = () => {
|
|
228
|
+
activeObservers.current.delete(listener);
|
|
184
229
|
unSub();
|
|
185
230
|
};
|
|
231
|
+
|
|
232
|
+
// Store the observer and unsubscribe function
|
|
233
|
+
activeObservers.current.set(listener, { observer, unsubscribe });
|
|
234
|
+
|
|
235
|
+
// Return an unsubscribe function that can be called to clean up the subscription
|
|
236
|
+
return unsubscribe;
|
|
186
237
|
},
|
|
187
238
|
// Return an immutable snapshot of the current bloc state
|
|
188
239
|
getSnapshot: (): BlocState<InstanceType<B>> | undefined => {
|
|
@@ -190,7 +241,55 @@ const useExternalBlocStore = <
|
|
|
190
241
|
if (!instance) {
|
|
191
242
|
return {} as BlocState<InstanceType<B>>;
|
|
192
243
|
}
|
|
193
|
-
|
|
244
|
+
|
|
245
|
+
const currentState = instance.state;
|
|
246
|
+
const currentDependencies = dependencyArray(currentState, previousStateRef.current);
|
|
247
|
+
|
|
248
|
+
// Check if dependencies have changed using the two-array comparison logic
|
|
249
|
+
const lastDeps = lastDependenciesRef.current;
|
|
250
|
+
let dependenciesChanged = false;
|
|
251
|
+
|
|
252
|
+
if (!lastDeps) {
|
|
253
|
+
// First time - dependencies changed
|
|
254
|
+
dependenciesChanged = true;
|
|
255
|
+
} else if (lastDeps.length !== currentDependencies.length) {
|
|
256
|
+
// Array structure changed
|
|
257
|
+
dependenciesChanged = true;
|
|
258
|
+
} else {
|
|
259
|
+
// Compare each array (state and class dependencies)
|
|
260
|
+
for (let arrayIndex = 0; arrayIndex < currentDependencies.length; arrayIndex++) {
|
|
261
|
+
const lastArray = lastDeps[arrayIndex] || [];
|
|
262
|
+
const newArray = currentDependencies[arrayIndex] || [];
|
|
263
|
+
|
|
264
|
+
if (lastArray.length !== newArray.length) {
|
|
265
|
+
dependenciesChanged = true;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Compare each dependency value using Object.is
|
|
270
|
+
for (let i = 0; i < newArray.length; i++) {
|
|
271
|
+
if (!Object.is(lastArray[i], newArray[i])) {
|
|
272
|
+
dependenciesChanged = true;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (dependenciesChanged) break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Update dependency tracking
|
|
282
|
+
lastDependenciesRef.current = currentDependencies;
|
|
283
|
+
|
|
284
|
+
// If dependencies haven't changed, return the same snapshot reference
|
|
285
|
+
// This prevents React from re-rendering when dependencies are stable
|
|
286
|
+
if (!dependenciesChanged && lastStableSnapshot.current) {
|
|
287
|
+
return lastStableSnapshot.current;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Dependencies changed - update and return new snapshot
|
|
291
|
+
lastStableSnapshot.current = currentState;
|
|
292
|
+
return currentState;
|
|
194
293
|
},
|
|
195
294
|
// Server snapshot mirrors the client snapshot in this implementation
|
|
196
295
|
getServerSnapshot: (): BlocState<InstanceType<B>> | undefined => {
|
|
@@ -201,7 +300,7 @@ const useExternalBlocStore = <
|
|
|
201
300
|
return instance.state;
|
|
202
301
|
},
|
|
203
302
|
}
|
|
204
|
-
}, [
|
|
303
|
+
}, []); // Store is stable - individual methods handle instance changes
|
|
205
304
|
|
|
206
305
|
return {
|
|
207
306
|
usedKeys,
|
|
@@ -209,6 +308,7 @@ const useExternalBlocStore = <
|
|
|
209
308
|
externalStore: state,
|
|
210
309
|
instance: blocInstance,
|
|
211
310
|
rid,
|
|
311
|
+
hasProxyTracking,
|
|
212
312
|
};
|
|
213
313
|
};
|
|
214
314
|
|