@blac/react 2.0.0 → 2.0.1
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 +207 -129
- package/dist/index.cjs +260 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +165 -0
- package/dist/index.d.ts +143 -77
- package/dist/index.js +257 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -28
- package/dist/index.cjs.js +0 -2
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.es.js +0 -116
- package/dist/index.es.js.map +0 -1
- package/src/externalBlocStore.ts +0 -93
- package/src/index.ts +0 -4
- package/src/useBloc.tsx +0 -261
package/README.md
CHANGED
|
@@ -1,194 +1,272 @@
|
|
|
1
1
|
# @blac/react
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- 🔄 Automatic re-rendering when relevant state changes
|
|
8
|
-
- 🎯 Fine-grained dependency tracking
|
|
9
|
-
- 🔍 Property access tracking for optimized updates
|
|
10
|
-
- 🎨 TypeScript support with full type inference
|
|
11
|
-
- ⚡️ Efficient state management with minimal boilerplate
|
|
12
|
-
- 🔄 Support for isolated and shared bloc instances
|
|
13
|
-
- 🎯 Custom dependency selectors for precise control
|
|
14
|
-
|
|
15
|
-
## Important: Arrow Functions Required
|
|
16
|
-
|
|
17
|
-
All methods in Bloc or Cubit classes must use arrow function syntax (`method = () => {}`) instead of the traditional method syntax (`method() {}`). This is because arrow functions automatically bind `this` to the class instance. Without this binding, methods called from React components would lose their context and could not access instance properties like `this.state` or `this.emit()`.
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
// Correct way to define methods in your Bloc/Cubit classes
|
|
21
|
-
class CounterBloc extends Cubit<CounterState> {
|
|
22
|
-
increment = () => {
|
|
23
|
-
this.emit({ ...this.state, count: this.state.count + 1 });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
decrement = () => {
|
|
27
|
-
this.emit({ ...this.state, count: this.state.count - 1 });
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Incorrect way (will cause issues when called from React):
|
|
32
|
-
class CounterBloc extends Cubit<CounterState> {
|
|
33
|
-
increment() { // ❌ Will lose 'this' context when called from components
|
|
34
|
-
this.emit({ ...this.state, count: this.state.count + 1 });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
```
|
|
3
|
+
React integration for the BlaC state management library with automatic re-render optimization.
|
|
38
4
|
|
|
39
5
|
## Installation
|
|
40
6
|
|
|
41
7
|
```bash
|
|
42
|
-
npm install @blac/react
|
|
8
|
+
npm install @blac/react @blac/core
|
|
43
9
|
# or
|
|
44
|
-
|
|
10
|
+
pnpm add @blac/react @blac/core
|
|
45
11
|
# or
|
|
46
|
-
|
|
12
|
+
yarn add @blac/react @blac/core
|
|
47
13
|
```
|
|
48
14
|
|
|
49
15
|
## Quick Start
|
|
50
16
|
|
|
51
17
|
```tsx
|
|
18
|
+
import { Cubit } from '@blac/core';
|
|
52
19
|
import { useBloc } from '@blac/react';
|
|
53
|
-
|
|
20
|
+
|
|
21
|
+
class CounterCubit extends Cubit<{ count: number }> {
|
|
22
|
+
constructor() {
|
|
23
|
+
super({ count: 0 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
increment = () => this.emit({ count: this.state.count + 1 });
|
|
27
|
+
decrement = () => this.emit({ count: this.state.count - 1 });
|
|
28
|
+
}
|
|
54
29
|
|
|
55
30
|
function Counter() {
|
|
56
|
-
const [state,
|
|
31
|
+
const [state, counter] = useBloc(CounterCubit);
|
|
57
32
|
|
|
58
33
|
return (
|
|
59
34
|
<div>
|
|
60
35
|
<p>Count: {state.count}</p>
|
|
61
|
-
<button onClick={
|
|
36
|
+
<button onClick={counter.increment}>+</button>
|
|
37
|
+
<button onClick={counter.decrement}>-</button>
|
|
62
38
|
</div>
|
|
63
39
|
);
|
|
64
40
|
}
|
|
65
41
|
```
|
|
66
42
|
|
|
67
|
-
##
|
|
43
|
+
## Hooks
|
|
68
44
|
|
|
69
|
-
###
|
|
45
|
+
### useBloc
|
|
70
46
|
|
|
71
|
-
|
|
47
|
+
Connects a component to a state container with automatic re-renders on state changes.
|
|
72
48
|
|
|
73
49
|
```tsx
|
|
74
|
-
const [state, bloc] = useBloc(
|
|
50
|
+
const [state, bloc, ref] = useBloc(MyBloc);
|
|
75
51
|
```
|
|
76
52
|
|
|
77
|
-
|
|
53
|
+
**Returns:** `[state, bloc, ref]`
|
|
54
|
+
|
|
55
|
+
- `state` - Current state (reactive)
|
|
56
|
+
- `bloc` - The bloc instance for calling methods
|
|
57
|
+
- `ref` - Component reference for isolated instances
|
|
78
58
|
|
|
79
|
-
|
|
59
|
+
#### Tracking Modes
|
|
60
|
+
|
|
61
|
+
**Auto-tracking (default):** Automatically detects which properties you access and only re-renders when those change.
|
|
80
62
|
|
|
81
63
|
```tsx
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
64
|
+
function UserProfile() {
|
|
65
|
+
const [state, user] = useBloc(UserBloc);
|
|
66
|
+
|
|
67
|
+
// Only re-renders when state.name changes
|
|
68
|
+
// (state.email changes won't cause re-render)
|
|
69
|
+
return <h1>{state.name}</h1>;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Manual dependencies:** Explicit dependency array like `useEffect`.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
function Counter() {
|
|
77
|
+
const [state] = useBloc(CounterBloc, {
|
|
78
|
+
dependencies: (state) => [state.count],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return <p>{state.count}</p>;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**No tracking:** Returns full state, re-renders on any change.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
function FullState() {
|
|
89
|
+
const [state] = useBloc(MyBloc, { autoTrack: false });
|
|
90
|
+
|
|
91
|
+
return <pre>{JSON.stringify(state)}</pre>;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### Options
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
useBloc(MyBloc, {
|
|
99
|
+
// Tracking mode
|
|
100
|
+
autoTrack: true, // Enable/disable auto-tracking (default: true)
|
|
101
|
+
dependencies: (state) => [state.prop], // Manual dependencies
|
|
102
|
+
|
|
103
|
+
// Instance management
|
|
104
|
+
instanceId: 'unique-id', // Custom instance identifier
|
|
105
|
+
props: { userId: 123 }, // Props passed to bloc
|
|
106
|
+
|
|
107
|
+
// Lifecycle callbacks
|
|
108
|
+
onMount: (bloc) => console.log('Mounted', bloc),
|
|
109
|
+
onUnmount: (bloc) => console.log('Unmounted', bloc),
|
|
87
110
|
});
|
|
88
111
|
```
|
|
89
112
|
|
|
90
|
-
###
|
|
113
|
+
### useBlocActions
|
|
91
114
|
|
|
92
|
-
|
|
115
|
+
Connects to a state container without subscribing to state changes. Use for calling actions without re-renders, or for stateless containers.
|
|
93
116
|
|
|
94
117
|
```tsx
|
|
95
|
-
|
|
96
|
-
|
|
118
|
+
const bloc = useBlocActions(MyBloc);
|
|
119
|
+
```
|
|
97
120
|
|
|
98
|
-
|
|
99
|
-
|
|
121
|
+
**Use cases:**
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// Stateless services (analytics, navigation, etc.)
|
|
125
|
+
function TrackButton() {
|
|
126
|
+
const analytics = useBlocActions(AnalyticsService);
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<button onClick={() => analytics.trackClick('button')}>Click me</button>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Actions-only (no state subscription)
|
|
134
|
+
function IncrementButton() {
|
|
135
|
+
const counter = useBlocActions(CounterCubit);
|
|
136
|
+
|
|
137
|
+
return <button onClick={counter.increment}>+</button>;
|
|
100
138
|
}
|
|
101
139
|
```
|
|
102
140
|
|
|
103
|
-
|
|
141
|
+
#### Options
|
|
104
142
|
|
|
105
143
|
```tsx
|
|
106
|
-
|
|
107
|
-
|
|
144
|
+
useBlocActions(MyBloc, {
|
|
145
|
+
instanceId: 'unique-id', // Custom instance identifier
|
|
146
|
+
props: { config: value }, // Props passed to bloc
|
|
147
|
+
onMount: (bloc) => bloc.initialize(),
|
|
148
|
+
onUnmount: (bloc) => bloc.cleanup(),
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Instance Management
|
|
153
|
+
|
|
154
|
+
### Shared Instances (Default)
|
|
155
|
+
|
|
156
|
+
By default, all components using the same bloc class share one instance:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
function ComponentA() {
|
|
160
|
+
const [state] = useBloc(CounterCubit);
|
|
161
|
+
// Uses shared instance
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function ComponentB() {
|
|
165
|
+
const [state] = useBloc(CounterCubit);
|
|
166
|
+
// Uses same shared instance
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Isolated Instances
|
|
171
|
+
|
|
172
|
+
For component-scoped state, use the `@blac({ isolated: true })` decorator:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { Cubit, blac } from '@blac/core';
|
|
176
|
+
|
|
177
|
+
@blac({ isolated: true })
|
|
178
|
+
class FormCubit extends Cubit<FormState> {
|
|
179
|
+
// ...
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function FormA() {
|
|
183
|
+
const [state] = useBloc(FormCubit);
|
|
184
|
+
// Gets its own instance
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function FormB() {
|
|
188
|
+
const [state] = useBloc(FormCubit);
|
|
189
|
+
// Gets a different instance
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Named Instances
|
|
194
|
+
|
|
195
|
+
Share instances across specific components with `instanceId`:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
function EditorA() {
|
|
199
|
+
const [state] = useBloc(EditorCubit, { instanceId: 'editor-1' });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function EditorB() {
|
|
203
|
+
const [state] = useBloc(EditorCubit, { instanceId: 'editor-1' });
|
|
204
|
+
// Same instance as EditorA
|
|
205
|
+
}
|
|
108
206
|
|
|
109
|
-
|
|
110
|
-
|
|
207
|
+
function EditorC() {
|
|
208
|
+
const [state] = useBloc(EditorCubit, { instanceId: 'editor-2' });
|
|
209
|
+
// Different instance
|
|
111
210
|
}
|
|
112
211
|
```
|
|
113
212
|
|
|
114
|
-
|
|
213
|
+
## Configuration
|
|
115
214
|
|
|
116
|
-
|
|
215
|
+
Configure global behavior:
|
|
117
216
|
|
|
118
217
|
```tsx
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
]
|
|
218
|
+
import { configureBlacReact } from '@blac/react';
|
|
219
|
+
|
|
220
|
+
configureBlacReact({
|
|
221
|
+
autoTrack: true, // Enable auto-tracking by default (default: true)
|
|
124
222
|
});
|
|
125
223
|
```
|
|
126
224
|
|
|
127
225
|
## API Reference
|
|
128
226
|
|
|
129
|
-
### useBloc
|
|
227
|
+
### useBloc
|
|
130
228
|
|
|
131
|
-
```
|
|
132
|
-
function useBloc<
|
|
133
|
-
|
|
134
|
-
options?:
|
|
135
|
-
): [
|
|
229
|
+
```tsx
|
|
230
|
+
function useBloc<T extends StateContainerConstructor>(
|
|
231
|
+
BlocClass: T,
|
|
232
|
+
options?: UseBlocOptions<T>,
|
|
233
|
+
): [ExtractState<T>, InstanceType<T>, ComponentRef];
|
|
136
234
|
```
|
|
137
235
|
|
|
138
|
-
|
|
236
|
+
### useBlocActions
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
function useBlocActions<T extends StateContainerConstructor>(
|
|
240
|
+
BlocClass: T,
|
|
241
|
+
options?: UseBlocActionsOptions<InstanceType<T>>,
|
|
242
|
+
): InstanceType<T>;
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### UseBlocOptions
|
|
246
|
+
|
|
247
|
+
| Option | Type | Description |
|
|
248
|
+
| -------------- | ------------------ | ------------------------------------ |
|
|
249
|
+
| `autoTrack` | `boolean` | Enable auto-tracking (default: true) |
|
|
250
|
+
| `dependencies` | `(state) => any[]` | Manual dependency selector |
|
|
251
|
+
| `instanceId` | `string \| number` | Custom instance identifier |
|
|
252
|
+
| `props` | `P` | Props passed to bloc constructor |
|
|
253
|
+
| `onMount` | `(bloc) => void` | Called when component mounts |
|
|
254
|
+
| `onUnmount` | `(bloc) => void` | Called when component unmounts |
|
|
255
|
+
|
|
256
|
+
### UseBlocActionsOptions
|
|
257
|
+
|
|
258
|
+
| Option | Type | Description |
|
|
259
|
+
| ------------ | ------------------ | -------------------------------- |
|
|
260
|
+
| `instanceId` | `string \| number` | Custom instance identifier |
|
|
261
|
+
| `props` | `P` | Props passed to bloc constructor |
|
|
262
|
+
| `onMount` | `(bloc) => void` | Called when component mounts |
|
|
263
|
+
| `onUnmount` | `(bloc) => void` | Called when component unmounts |
|
|
264
|
+
|
|
265
|
+
## Compatibility
|
|
139
266
|
|
|
140
|
-
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
- `dependencySelector?: BlocHookDependencyArrayFn<B>` - Function to select dependencies for re-renders
|
|
144
|
-
|
|
145
|
-
## Best Practices
|
|
146
|
-
|
|
147
|
-
1. **Use Isolated Blocs**: When you need component-specific state, use isolated blocs:
|
|
148
|
-
```tsx
|
|
149
|
-
class MyIsolatedBloc extends BlocBase {
|
|
150
|
-
static isolated = true;
|
|
151
|
-
// ... rest of your bloc implementation
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
2. **Use Custom Identifiers**: When you need multiple independent instances of the same Bloc type, use custom identifiers to manage different state contexts:
|
|
156
|
-
```tsx
|
|
157
|
-
// In a chat application with multiple chat rooms
|
|
158
|
-
function ChatRoom({ roomId }: { roomId: string }) {
|
|
159
|
-
const [state, chatBloc] = useBloc(ChatBloc, {
|
|
160
|
-
id: `chat-${roomId}`, // Each room gets its own instance
|
|
161
|
-
props: { roomId }
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<div>
|
|
166
|
-
<h2>Room: {roomId}</h2>
|
|
167
|
-
{state.messages.map(msg => (
|
|
168
|
-
<Message key={msg.id} message={msg} />
|
|
169
|
-
))}
|
|
170
|
-
</div>
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Usage:
|
|
175
|
-
function ChatApp() {
|
|
176
|
-
return (
|
|
177
|
-
<div>
|
|
178
|
-
<ChatRoom roomId="general" />
|
|
179
|
-
<ChatRoom roomId="support" />
|
|
180
|
-
</div>
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
3. **Optimize Re-renders**: Let the hook track dependencies automatically unless you have a specific need for custom dependency tracking.
|
|
186
|
-
|
|
187
|
-
4. **Type Safety**: Take advantage of TypeScript's type inference for better development experience and catch errors early.
|
|
188
|
-
|
|
189
|
-
## Contributing
|
|
190
|
-
|
|
191
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
267
|
+
- React 18.0+ or React 19.0+
|
|
268
|
+
- Works with React Compiler
|
|
269
|
+
- Supports concurrent rendering via `useSyncExternalStore`
|
|
192
270
|
|
|
193
271
|
## License
|
|
194
272
|
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
let react = require("react");
|
|
2
|
+
let _blac_core = require("@blac/core");
|
|
3
|
+
|
|
4
|
+
//#region src/utils/instance-keys.ts
|
|
5
|
+
/**
|
|
6
|
+
* Instance key generation utilities for React integration
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Generate an instance key for a bloc
|
|
10
|
+
*
|
|
11
|
+
* Logic:
|
|
12
|
+
* - If user provides instanceId, use it (convert number to string)
|
|
13
|
+
* - If isolated, generate or reuse a unique key for this component
|
|
14
|
+
* - Otherwise, return undefined (use default key)
|
|
15
|
+
*
|
|
16
|
+
* @param componentRef - React component reference (persists across remounts)
|
|
17
|
+
* @param isIsolated - Whether the bloc is isolated
|
|
18
|
+
* @param providedId - User-provided instance ID (from options)
|
|
19
|
+
* @returns Instance key string or undefined for default
|
|
20
|
+
*/
|
|
21
|
+
function generateInstanceKey(componentRef, isIsolated, providedId) {
|
|
22
|
+
if (providedId !== void 0) return typeof providedId === "number" ? String(providedId) : providedId;
|
|
23
|
+
if (isIsolated) {
|
|
24
|
+
if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = (0, _blac_core.generateIsolatedKey)();
|
|
25
|
+
return componentRef.__blocInstanceId;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/config.ts
|
|
31
|
+
const defaultConfig = { autoTrack: true };
|
|
32
|
+
let globalConfig = { ...defaultConfig };
|
|
33
|
+
/**
|
|
34
|
+
* Configure global defaults for @blac/react hooks.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { configureBlacReact } from '@blac/react';
|
|
39
|
+
*
|
|
40
|
+
* // Disable auto-tracking globally
|
|
41
|
+
* configureBlacReact({
|
|
42
|
+
* autoTrack: false
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @param config - Partial configuration to merge with defaults
|
|
47
|
+
*/
|
|
48
|
+
function configureBlacReact(config) {
|
|
49
|
+
globalConfig = {
|
|
50
|
+
...globalConfig,
|
|
51
|
+
...config
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the current global configuration.
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
function getBlacReactConfig() {
|
|
59
|
+
return globalConfig;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Reset configuration to defaults (useful for testing).
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
function resetBlacReactConfig() {
|
|
66
|
+
globalConfig = { ...defaultConfig };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/useBloc.ts
|
|
71
|
+
function determineTrackingMode(options) {
|
|
72
|
+
const globalConfig$1 = getBlacReactConfig();
|
|
73
|
+
const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig$1.autoTrack;
|
|
74
|
+
return {
|
|
75
|
+
useManualDeps: options?.dependencies !== void 0,
|
|
76
|
+
autoTrackEnabled
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* React hook that connects a component to a state container with automatic re-render on state changes.
|
|
81
|
+
*
|
|
82
|
+
* Supports three tracking modes:
|
|
83
|
+
* - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy
|
|
84
|
+
* - **Manual dependencies**: Explicit dependency array like useEffect
|
|
85
|
+
* - **No tracking**: Returns full state without optimization
|
|
86
|
+
*
|
|
87
|
+
* **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).
|
|
88
|
+
* Use `useBlocActions()` instead for stateless containers.
|
|
89
|
+
*
|
|
90
|
+
* @template T - The state container constructor type (inferred from BlocClass)
|
|
91
|
+
* @param BlocClass - The state container class to connect to (must not be stateless)
|
|
92
|
+
* @param options - Configuration options for tracking mode and instance management
|
|
93
|
+
* @returns Tuple with [state, bloc instance, ref]
|
|
94
|
+
*
|
|
95
|
+
* @example Basic usage
|
|
96
|
+
* ```ts
|
|
97
|
+
* const [state, myBloc, ref] = useBloc(MyBloc);
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* @example With manual dependencies
|
|
101
|
+
* ```ts
|
|
102
|
+
* const [state, myBloc] = useBloc(MyBloc, {
|
|
103
|
+
* dependencies: (state) => [state.count]
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @example With isolated instance
|
|
108
|
+
* ```ts
|
|
109
|
+
* const [state, myBloc] = useBloc(MyBloc, {
|
|
110
|
+
* instanceId: 'unique-id'
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
function useBloc(BlocClass, options) {
|
|
115
|
+
const componentRef = (0, react.useRef)({});
|
|
116
|
+
const initialPropsRef = (0, react.useRef)(options?.props);
|
|
117
|
+
const isStateless = (0, _blac_core.isStatelessClass)(BlocClass);
|
|
118
|
+
const isIsolated = (0, _blac_core.isIsolatedClass)(BlocClass);
|
|
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
|
+
}
|
|
132
|
+
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
133
|
+
const instance = (0, _blac_core.acquire)(BlocClass, instanceKey$1, { props: initialPropsRef.current });
|
|
134
|
+
if (initialPropsRef.current !== void 0) instance.updateProps(initialPropsRef.current);
|
|
135
|
+
const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
|
|
136
|
+
let subscribeFn;
|
|
137
|
+
let getSnapshotFn;
|
|
138
|
+
let adapterState$1;
|
|
139
|
+
if (useManualDeps && options?.dependencies) {
|
|
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 });
|
|
143
|
+
} else if (!autoTrackEnabled) {
|
|
144
|
+
adapterState$1 = (0, _blac_core.noTrackInit)(instance);
|
|
145
|
+
subscribeFn = (0, _blac_core.noTrackSubscribe)(instance);
|
|
146
|
+
getSnapshotFn = (0, _blac_core.noTrackSnapshot)(instance);
|
|
147
|
+
} else {
|
|
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);
|
|
151
|
+
}
|
|
152
|
+
return [
|
|
153
|
+
adapterState$1.proxiedBloc,
|
|
154
|
+
subscribeFn,
|
|
155
|
+
getSnapshotFn,
|
|
156
|
+
instanceKey$1,
|
|
157
|
+
adapterState$1,
|
|
158
|
+
instance
|
|
159
|
+
];
|
|
160
|
+
}, [
|
|
161
|
+
BlocClass,
|
|
162
|
+
isStateless,
|
|
163
|
+
isIsolated,
|
|
164
|
+
options?.instanceId
|
|
165
|
+
]);
|
|
166
|
+
const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
167
|
+
const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
|
|
168
|
+
const externalDepsManager = (0, react.useRef)(new _blac_core.ExternalDepsManager());
|
|
169
|
+
(0, react.useEffect)(() => {
|
|
170
|
+
if (isStateless) return;
|
|
171
|
+
if (options?.props !== initialPropsRef.current) rawInstance.updateProps(options?.props);
|
|
172
|
+
}, [
|
|
173
|
+
options?.props,
|
|
174
|
+
rawInstance,
|
|
175
|
+
isStateless
|
|
176
|
+
]);
|
|
177
|
+
(0, react.useEffect)(() => {
|
|
178
|
+
if (isStateless || !adapterState) return;
|
|
179
|
+
(0, _blac_core.disableGetterTracking)(adapterState, rawInstance);
|
|
180
|
+
externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
|
|
181
|
+
});
|
|
182
|
+
(0, react.useEffect)(() => {
|
|
183
|
+
if (isStateless) return;
|
|
184
|
+
if (options?.onMount) options.onMount(bloc);
|
|
185
|
+
return () => {
|
|
186
|
+
externalDepsManager.current.cleanup();
|
|
187
|
+
if (options?.onUnmount) options.onUnmount(bloc);
|
|
188
|
+
(0, _blac_core.release)(BlocClass, instanceKey);
|
|
189
|
+
if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
|
|
190
|
+
};
|
|
191
|
+
}, []);
|
|
192
|
+
return [
|
|
193
|
+
state,
|
|
194
|
+
bloc,
|
|
195
|
+
componentRef
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/useBlocActions.ts
|
|
201
|
+
/**
|
|
202
|
+
* React hook that connects to a state container instance without triggering re-renders.
|
|
203
|
+
* Use this when you only need to call actions on the bloc without subscribing to state changes.
|
|
204
|
+
*
|
|
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
|
+
*
|
|
208
|
+
* @template T - The state container constructor type (inferred from BlocClass)
|
|
209
|
+
* @param BlocClass - The state container class to connect to (supports both stateful and stateless)
|
|
210
|
+
* @param options - Configuration options for instance management and lifecycle
|
|
211
|
+
* @returns The state container instance for calling actions
|
|
212
|
+
*
|
|
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)
|
|
224
|
+
* ```ts
|
|
225
|
+
* const myBloc = useBlocActions(MyBloc);
|
|
226
|
+
* myBloc.someMethod(); // Won't cause re-renders
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* @example With isolated instance
|
|
230
|
+
* ```ts
|
|
231
|
+
* const myBloc = useBlocActions(MyBloc, {
|
|
232
|
+
* instanceId: 'unique-id'
|
|
233
|
+
* });
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
function useBlocActions(BlocClass, options) {
|
|
237
|
+
const componentRef = (0, react.useRef)({});
|
|
238
|
+
const initialPropsRef = (0, react.useRef)(options?.props);
|
|
239
|
+
const [bloc, instanceKey] = (0, react.useMemo)(() => {
|
|
240
|
+
const isIsolated = (0, _blac_core.isIsolatedClass)(BlocClass);
|
|
241
|
+
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
242
|
+
return [(0, _blac_core.acquire)(BlocClass, instanceKey$1, { props: initialPropsRef.current }), instanceKey$1];
|
|
243
|
+
}, [BlocClass]);
|
|
244
|
+
(0, react.useEffect)(() => {
|
|
245
|
+
if (options?.onMount) options.onMount(bloc);
|
|
246
|
+
return () => {
|
|
247
|
+
if (options?.onUnmount) options.onUnmount(bloc);
|
|
248
|
+
(0, _blac_core.release)(BlocClass, instanceKey);
|
|
249
|
+
if ((0, _blac_core.isIsolatedClass)(BlocClass) && !bloc.isDisposed) bloc.dispose();
|
|
250
|
+
};
|
|
251
|
+
}, []);
|
|
252
|
+
return bloc;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
exports.configureBlacReact = configureBlacReact;
|
|
257
|
+
exports.resetBlacReactConfig = resetBlacReactConfig;
|
|
258
|
+
exports.useBloc = useBloc;
|
|
259
|
+
exports.useBlocActions = useBlocActions;
|
|
260
|
+
//# sourceMappingURL=index.cjs.map
|