@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 CHANGED
@@ -1,264 +1,272 @@
1
1
  # @blac/react
2
2
 
3
- A powerful React integration for the Blac state management library, providing seamless integration between React components and Blac's reactive state management system.
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
- yarn add @blac/react
10
+ pnpm add @blac/react @blac/core
46
11
  # or
47
- pnpm add @blac/react
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
- import { CounterBloc } from './CounterBloc';
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, counterBloc] = useBloc(CounterBloc);
31
+ const [state, counter] = useBloc(CounterCubit);
58
32
 
59
33
  return (
60
34
  <div>
61
35
  <p>Count: {state.count}</p>
62
- <button onClick={() => counterBloc.increment()}>Increment</button>
36
+ <button onClick={counter.increment}>+</button>
37
+ <button onClick={counter.decrement}>-</button>
63
38
  </div>
64
39
  );
65
40
  }
66
41
  ```
67
42
 
68
- ## Usage
43
+ ## Hooks
69
44
 
70
- ### Basic Usage
45
+ ### useBloc
71
46
 
72
- The `useBloc` hook provides a simple way to connect your React components to Blac's state management system:
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(YourBloc);
50
+ const [state, bloc, ref] = useBloc(MyBloc);
76
51
  ```
77
52
 
78
- ### Advanced Configuration
53
+ **Returns:** `[state, bloc, ref]`
79
54
 
80
- The hook accepts configuration options for more control:
81
-
82
- ```tsx
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
- ### Automatic Dependency Tracking
59
+ #### Tracking Modes
92
60
 
93
- The hook automatically tracks which state properties and bloc instance properties are accessed in your component and only triggers re-renders when those specific values change:
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, userBloc] = useBloc(UserBloc);
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
- This also works for getters and computed properties on the Bloc or Cubit class:
73
+ **Manual dependencies:** Explicit dependency array like `useEffect`.
105
74
 
106
75
  ```tsx
107
- function UserProfile() {
108
- const [state, userBloc] = useBloc(UserBloc);
76
+ function Counter() {
77
+ const [state] = useBloc(CounterBloc, {
78
+ dependencies: (state) => [state.count],
79
+ });
109
80
 
110
- // Only re-renders when:
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
- #### How It Works
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
- 1. **State Properties**: When you access `state.propertyName`, it's automatically tracked
122
- 2. **Instance Properties**: When you access `bloc.computedValue`, it's automatically tracked
123
- 3. **Intelligent Comparison**: The system separately tracks state dependencies and instance dependencies to handle edge cases where properties are dynamically added/removed
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
- #### Configuring Proxy Tracking
91
+ return <pre>{JSON.stringify(state)}</pre>;
92
+ }
93
+ ```
127
94
 
128
- By default, BlaC uses proxy-based dependency tracking for optimal performance. You can disable this globally if needed:
95
+ #### Options
129
96
 
130
97
  ```tsx
131
- import { Blac } from '@blac/core';
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
- // Disable automatic dependency tracking globally
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
- // Now components will re-render on ANY state change
137
- // unless you provide manual dependencies
117
+ ```tsx
118
+ const bloc = useBlocActions(MyBloc);
138
119
  ```
139
120
 
140
- When proxy tracking is disabled:
141
- - Components re-render on any state change (similar to traditional state management)
142
- - Manual dependencies via the `selector` option still work as expected
143
- - Useful for debugging or when proxy behavior causes issues
121
+ **Use cases:**
122
+
123
+ ```tsx
124
+ // Stateless services (analytics, navigation, etc.)
125
+ function TrackButton() {
126
+ const analytics = useBlocActions(AnalyticsService);
144
127
 
145
- For more configuration options, see the [@blac/core documentation](https://www.npmjs.com/package/@blac/core).
128
+ return (
129
+ <button onClick={() => analytics.trackClick('button')}>Click me</button>
130
+ );
131
+ }
146
132
 
147
- ### Custom Dependency Selector
133
+ // Actions-only (no state subscription)
134
+ function IncrementButton() {
135
+ const counter = useBlocActions(CounterCubit);
148
136
 
149
- For more control over when your component re-renders, you can provide a custom dependency selector. The selector function receives the current state, previous state, and bloc instance, and should return an array of values to track:
137
+ return <button onClick={counter.increment}>+</button>;
138
+ }
139
+ ```
140
+
141
+ #### Options
150
142
 
151
143
  ```tsx
152
- const [state, bloc] = useBloc(YourBloc, {
153
- selector: (currentState, previousState, instance) => [
154
- currentState.specificField,
155
- currentState.anotherField,
156
- instance.computedValue // You can also track computed properties from the bloc instance
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
- The component will only re-render when any of the values in the returned array change (using `Object.is` comparison, similar to React's `useEffect` dependency array).
152
+ ## Instance Management
153
+
154
+ ### Shared Instances (Default)
162
155
 
163
- #### Examples of Custom Selectors
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
- const [state, userBloc] = useBloc(UserBloc, {
168
- selector: (currentState) => [
169
- currentState.name,
170
- currentState.email
171
- ] // Only re-render when name or email changes, ignore other properties
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
- **Track computed values:**
170
+ ### Isolated Instances
171
+
172
+ For component-scoped state, use the `@blac({ isolated: true })` decorator:
173
+
176
174
  ```tsx
177
- const [state, shoppingCartBloc] = useBloc(ShoppingCartBloc, {
178
- selector: (currentState, previousState, instance) => [
179
- instance.totalPrice, // Computed getter
180
- currentState.items.length // Number of items
181
- ] // Only re-render when total price or item count changes
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
- **Compare with previous state:**
193
+ ### Named Instances
194
+
195
+ Share instances across specific components with `instanceId`:
196
+
186
197
  ```tsx
187
- const [state, chatBloc] = useBloc(ChatBloc, {
188
- selector: (currentState, previousState) => [
189
- currentState.messages.length > (previousState?.messages.length || 0) ? 'new-message' : 'no-change'
190
- ] // Only re-render when new messages are added, not when existing messages change
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 Hook
227
+ ### useBloc
197
228
 
198
- ```typescript
199
- function useBloc<B extends BlocConstructor<BlocGeneric>>(
200
- bloc: B,
201
- options?: BlocHookOptions<InstanceType<B>>
202
- ): [BlocState<InstanceType<B>>, InstanceType<B>]
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
- #### Options
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
- - `id?: string` - Custom identifier for the bloc instance
208
- - `props?: InferPropsFromGeneric<B>` - Props to pass to the bloc
209
- - `onMount?: (bloc: B) => void` - Callback function invoked when the react component (the consumer) is connected to the bloc instance
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