@blac/react 2.0.0-rn-12 → 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 +185 -177
- 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 +151 -45
- package/dist/index.js +257 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -25
- package/dist/index.cjs.js +0 -2
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.es.js +0 -111
- package/dist/index.es.js.map +0 -1
- package/src/__tests__/useBloc.props.test.tsx +0 -413
- package/src/__tests__/useBloc.proxyConfig.test.tsx +0 -228
- package/src/__tests__/useBloc.tracking.test.tsx +0 -198
- package/src/index.ts +0 -3
- package/src/useBloc.ts +0 -152
- package/src/useExternalBlocStore.ts +0 -140
package/README.md
CHANGED
|
@@ -1,264 +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 with access to state, previous state, and instance
|
|
14
|
-
- 🚀 Optimized re-rendering with intelligent snapshot comparison
|
|
15
|
-
|
|
16
|
-
## Important: Arrow Functions Required
|
|
17
|
-
|
|
18
|
-
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()`.
|
|
19
|
-
|
|
20
|
-
```tsx
|
|
21
|
-
// Correct way to define methods in your Bloc/Cubit classes
|
|
22
|
-
class CounterBloc extends Cubit<CounterState> {
|
|
23
|
-
increment = () => {
|
|
24
|
-
this.emit({ ...this.state, count: this.state.count + 1 });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
decrement = () => {
|
|
28
|
-
this.emit({ ...this.state, count: this.state.count - 1 });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Incorrect way (will cause issues when called from React):
|
|
33
|
-
class CounterBloc extends Cubit<CounterState> {
|
|
34
|
-
increment() { // ❌ Will lose 'this' context when called from components
|
|
35
|
-
this.emit({ ...this.state, count: this.state.count + 1 });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
```
|
|
3
|
+
React integration for the BlaC state management library with automatic re-render optimization.
|
|
39
4
|
|
|
40
5
|
## Installation
|
|
41
6
|
|
|
42
7
|
```bash
|
|
43
|
-
npm install @blac/react
|
|
8
|
+
npm install @blac/react @blac/core
|
|
44
9
|
# or
|
|
45
|
-
|
|
10
|
+
pnpm add @blac/react @blac/core
|
|
46
11
|
# or
|
|
47
|
-
|
|
12
|
+
yarn add @blac/react @blac/core
|
|
48
13
|
```
|
|
49
14
|
|
|
50
15
|
## Quick Start
|
|
51
16
|
|
|
52
17
|
```tsx
|
|
18
|
+
import { Cubit } from '@blac/core';
|
|
53
19
|
import { useBloc } from '@blac/react';
|
|
54
|
-
|
|
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
|
+
}
|
|
55
29
|
|
|
56
30
|
function Counter() {
|
|
57
|
-
const [state,
|
|
31
|
+
const [state, counter] = useBloc(CounterCubit);
|
|
58
32
|
|
|
59
33
|
return (
|
|
60
34
|
<div>
|
|
61
35
|
<p>Count: {state.count}</p>
|
|
62
|
-
<button onClick={
|
|
36
|
+
<button onClick={counter.increment}>+</button>
|
|
37
|
+
<button onClick={counter.decrement}>-</button>
|
|
63
38
|
</div>
|
|
64
39
|
);
|
|
65
40
|
}
|
|
66
41
|
```
|
|
67
42
|
|
|
68
|
-
##
|
|
43
|
+
## Hooks
|
|
69
44
|
|
|
70
|
-
###
|
|
45
|
+
### useBloc
|
|
71
46
|
|
|
72
|
-
|
|
47
|
+
Connects a component to a state container with automatic re-renders on state changes.
|
|
73
48
|
|
|
74
49
|
```tsx
|
|
75
|
-
const [state, bloc] = useBloc(
|
|
50
|
+
const [state, bloc, ref] = useBloc(MyBloc);
|
|
76
51
|
```
|
|
77
52
|
|
|
78
|
-
|
|
53
|
+
**Returns:** `[state, bloc, ref]`
|
|
79
54
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const [state, bloc] = useBloc(YourBloc, {
|
|
84
|
-
id: 'custom-id', // Optional: Custom identifier for the bloc
|
|
85
|
-
props: { /* ... */ }, // Optional: Props to pass to the bloc
|
|
86
|
-
onMount: (bloc) => { /* ... */ }, // Optional: Callback when bloc is mounted (similar to useEffect(<>, []))
|
|
87
|
-
selector: (currentState, previousState, instance) => [/* ... */], // Optional: Custom dependency tracking
|
|
88
|
-
});
|
|
89
|
-
```
|
|
55
|
+
- `state` - Current state (reactive)
|
|
56
|
+
- `bloc` - The bloc instance for calling methods
|
|
57
|
+
- `ref` - Component reference for isolated instances
|
|
90
58
|
|
|
91
|
-
|
|
59
|
+
#### Tracking Modes
|
|
92
60
|
|
|
93
|
-
|
|
61
|
+
**Auto-tracking (default):** Automatically detects which properties you access and only re-renders when those change.
|
|
94
62
|
|
|
95
63
|
```tsx
|
|
96
64
|
function UserProfile() {
|
|
97
|
-
const [state,
|
|
65
|
+
const [state, user] = useBloc(UserBloc);
|
|
98
66
|
|
|
99
67
|
// Only re-renders when state.name changes
|
|
68
|
+
// (state.email changes won't cause re-render)
|
|
100
69
|
return <h1>{state.name}</h1>;
|
|
101
70
|
}
|
|
102
71
|
```
|
|
103
72
|
|
|
104
|
-
|
|
73
|
+
**Manual dependencies:** Explicit dependency array like `useEffect`.
|
|
105
74
|
|
|
106
75
|
```tsx
|
|
107
|
-
function
|
|
108
|
-
const [state
|
|
76
|
+
function Counter() {
|
|
77
|
+
const [state] = useBloc(CounterBloc, {
|
|
78
|
+
dependencies: (state) => [state.count],
|
|
79
|
+
});
|
|
109
80
|
|
|
110
|
-
|
|
111
|
-
// - state.firstName changes, OR
|
|
112
|
-
// - state.lastName changes (because the getter accesses these properties)
|
|
113
|
-
return <h1>{userBloc.fullName}</h1>; // Assuming fullName is a getter
|
|
81
|
+
return <p>{state.count}</p>;
|
|
114
82
|
}
|
|
115
83
|
```
|
|
116
84
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
The dependency tracking system uses JavaScript Proxies to monitor property access during component renders:
|
|
85
|
+
**No tracking:** Returns full state, re-renders on any change.
|
|
120
86
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
4. **Optimized Updates**: Components only re-render when tracked dependencies actually change their values
|
|
87
|
+
```tsx
|
|
88
|
+
function FullState() {
|
|
89
|
+
const [state] = useBloc(MyBloc, { autoTrack: false });
|
|
125
90
|
|
|
126
|
-
|
|
91
|
+
return <pre>{JSON.stringify(state)}</pre>;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
127
94
|
|
|
128
|
-
|
|
95
|
+
#### Options
|
|
129
96
|
|
|
130
97
|
```tsx
|
|
131
|
-
|
|
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),
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### useBlocActions
|
|
132
114
|
|
|
133
|
-
|
|
134
|
-
Blac.setConfig({ proxyDependencyTracking: false });
|
|
115
|
+
Connects to a state container without subscribing to state changes. Use for calling actions without re-renders, or for stateless containers.
|
|
135
116
|
|
|
136
|
-
|
|
137
|
-
|
|
117
|
+
```tsx
|
|
118
|
+
const bloc = useBlocActions(MyBloc);
|
|
138
119
|
```
|
|
139
120
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
121
|
+
**Use cases:**
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// Stateless services (analytics, navigation, etc.)
|
|
125
|
+
function TrackButton() {
|
|
126
|
+
const analytics = useBlocActions(AnalyticsService);
|
|
144
127
|
|
|
145
|
-
|
|
128
|
+
return (
|
|
129
|
+
<button onClick={() => analytics.trackClick('button')}>Click me</button>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
146
132
|
|
|
147
|
-
|
|
133
|
+
// Actions-only (no state subscription)
|
|
134
|
+
function IncrementButton() {
|
|
135
|
+
const counter = useBlocActions(CounterCubit);
|
|
148
136
|
|
|
149
|
-
|
|
137
|
+
return <button onClick={counter.increment}>+</button>;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Options
|
|
150
142
|
|
|
151
143
|
```tsx
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
]
|
|
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(),
|
|
158
149
|
});
|
|
159
150
|
```
|
|
160
151
|
|
|
161
|
-
|
|
152
|
+
## Instance Management
|
|
153
|
+
|
|
154
|
+
### Shared Instances (Default)
|
|
162
155
|
|
|
163
|
-
|
|
156
|
+
By default, all components using the same bloc class share one instance:
|
|
164
157
|
|
|
165
|
-
**Track only specific state properties:**
|
|
166
158
|
```tsx
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
}
|
|
173
168
|
```
|
|
174
169
|
|
|
175
|
-
|
|
170
|
+
### Isolated Instances
|
|
171
|
+
|
|
172
|
+
For component-scoped state, use the `@blac({ isolated: true })` decorator:
|
|
173
|
+
|
|
176
174
|
```tsx
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
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
|
+
}
|
|
183
191
|
```
|
|
184
192
|
|
|
185
|
-
|
|
193
|
+
### Named Instances
|
|
194
|
+
|
|
195
|
+
Share instances across specific components with `instanceId`:
|
|
196
|
+
|
|
186
197
|
```tsx
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
+
}
|
|
206
|
+
|
|
207
|
+
function EditorC() {
|
|
208
|
+
const [state] = useBloc(EditorCubit, { instanceId: 'editor-2' });
|
|
209
|
+
// Different instance
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Configuration
|
|
214
|
+
|
|
215
|
+
Configure global behavior:
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import { configureBlacReact } from '@blac/react';
|
|
219
|
+
|
|
220
|
+
configureBlacReact({
|
|
221
|
+
autoTrack: true, // Enable auto-tracking by default (default: true)
|
|
191
222
|
});
|
|
192
223
|
```
|
|
193
224
|
|
|
194
225
|
## API Reference
|
|
195
226
|
|
|
196
|
-
### useBloc
|
|
227
|
+
### useBloc
|
|
197
228
|
|
|
198
|
-
```
|
|
199
|
-
function useBloc<
|
|
200
|
-
|
|
201
|
-
options?:
|
|
202
|
-
): [
|
|
229
|
+
```tsx
|
|
230
|
+
function useBloc<T extends StateContainerConstructor>(
|
|
231
|
+
BlocClass: T,
|
|
232
|
+
options?: UseBlocOptions<T>,
|
|
233
|
+
): [ExtractState<T>, InstanceType<T>, ComponentRef];
|
|
203
234
|
```
|
|
204
235
|
|
|
205
|
-
|
|
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
|
|
206
266
|
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
- `selector?: (currentState: BlocState<InstanceType<B>>, previousState: BlocState<InstanceType<B>> | undefined, instance: InstanceType<B>) => unknown[]` - Function to select dependencies for re-renders
|
|
211
|
-
|
|
212
|
-
## Best Practices
|
|
213
|
-
|
|
214
|
-
1. **Use Isolated Blocs**: When you need component-specific state, use isolated blocs:
|
|
215
|
-
```tsx
|
|
216
|
-
class MyIsolatedBloc extends BlocBase {
|
|
217
|
-
static isolated = true;
|
|
218
|
-
// ... rest of your bloc implementation
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
2. **Use Custom Identifiers**: When you need multiple independent instances of the same Bloc type, use custom identifiers to manage different state contexts:
|
|
223
|
-
```tsx
|
|
224
|
-
// In a chat application with multiple chat rooms
|
|
225
|
-
function ChatRoom({ roomId }: { roomId: string }) {
|
|
226
|
-
const [state, chatBloc] = useBloc(ChatBloc, {
|
|
227
|
-
id: `chat-${roomId}`, // Each room gets its own instance
|
|
228
|
-
props: { roomId }
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
return (
|
|
232
|
-
<div>
|
|
233
|
-
<h2>Room: {roomId}</h2>
|
|
234
|
-
{state.messages.map(msg => (
|
|
235
|
-
<Message key={msg.id} message={msg} />
|
|
236
|
-
))}
|
|
237
|
-
</div>
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Usage:
|
|
242
|
-
function ChatApp() {
|
|
243
|
-
return (
|
|
244
|
-
<div>
|
|
245
|
-
<ChatRoom roomId="general" />
|
|
246
|
-
<ChatRoom roomId="support" />
|
|
247
|
-
</div>
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
3. **Choose the Right Dependency Strategy**:
|
|
253
|
-
- **Use automatic tracking** (default) for most cases - it's efficient and requires no setup
|
|
254
|
-
- **Use custom selectors** when you need complex logic, computed comparisons, or want to ignore certain property changes
|
|
255
|
-
- **Avoid custom selectors** for simple property access - automatic tracking is more efficient
|
|
256
|
-
|
|
257
|
-
4. **Type Safety**: Take advantage of TypeScript's type inference for better development experience and catch errors early.
|
|
258
|
-
|
|
259
|
-
## Contributing
|
|
260
|
-
|
|
261
|
-
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`
|
|
262
270
|
|
|
263
271
|
## License
|
|
264
272
|
|