| name | blac-development |
| description | Develop with BlaC state management library for React. Use when creating Cubits, Vertices, using useBloc/useBlocActions hooks, managing state containers, or implementing inter-bloc communication patterns. |
BlaC Development Skill
BlaC is a TypeScript state management library with React integration using proxy-based dependency tracking for optimal re-renders.
Installation
pnpm add @blac/core @blac/react
Core Concepts
Cubit - Simple State Container
Use Cubit for direct state mutations without events. Best for most use cases.
import { Cubit } from '@blac/core';
class CounterCubit extends Cubit<{ count: number }> {
constructor() {
super({ count: 0 }); // initial state (must be an object)
}
// IMPORTANT: Always use arrow functions for React compatibility
increment = () => {
this.emit({ count: this.state.count + 1 });
};
decrement = () => {
this.emit({ count: this.state.count - 1 });
};
}
State Update Methods:
emit(newState)- Emit new state directlyupdate(fn)- Update with function(current) => nextpatch(partial)- Shallow merge partial state (object state only)
Vertex - Event-Driven State Container
Use Vertex for event-driven architectures with explicit state transitions.
import { Vertex } from '@blac/core';
// Define events as discriminated union
type CounterEvent =
| { type: 'increment'; amount: number }
| { type: 'decrement' };
class CounterVertex extends Vertex<{ count: number }, CounterEvent> {
constructor() {
super({ count: 0 });
// TypeScript enforces exhaustive handling - all event types must be handled
this.createHandlers({
increment: (event, emit) => {
emit({ count: this.state.count + event.amount });
},
decrement: (_, emit) => {
emit({ count: this.state.count - 1 });
},
});
}
// Convenience methods
increment = (amount = 1) => this.add({ type: 'increment', amount });
decrement = () => this.add({ type: 'decrement' });
}
Class Configuration with @blac() Decorator
import { blac, Cubit } from '@blac/core';
// Isolated: Each component gets its own instance
@blac({ isolated: true })
class FormBloc extends Cubit<FormState> {}
// KeepAlive: Never auto-dispose when ref count reaches 0
@blac({ keepAlive: true })
class AuthBloc extends Cubit<AuthState> {}
// Exclude from DevTools (prevents infinite loops)
@blac({ excludeFromDevTools: true })
class InternalBloc extends Cubit<State> {}
// Function syntax (no decorator support)
const MyBloc = blac({ isolated: true })(class extends Cubit<State> {});
Note: BlacOptions is a union type - only ONE option can be specified at a time.
React Integration
useBloc Hook - Automatic Dependency Tracking
import { useBloc } from '@blac/react';
function Counter() {
const [state, cubit] = useBloc(CounterCubit);
// Only re-renders when accessed properties change
return (
<div>
<p>Count: {state.count}</p>
<button onClick={cubit.increment}>+</button>
</div>
);
}
Returns: [state, blocInstance, componentRef]
useBloc Options
const [state, bloc] = useBloc(MyBloc, {
props: { userId: '123' }, // Constructor arguments
instanceId: 'main', // Custom instance ID for shared blocs
dependencies: (state, bloc) => [state.count], // Manual dependency tracking
autoTrack: false, // Disable automatic tracking
disableGetterCache: false, // Disable getter value caching (advanced)
onMount: (bloc) => bloc.fetchData(), // Lifecycle callbacks
onUnmount: (bloc) => bloc.cleanup(),
});
useBlocActions Hook - Actions Only (No State Subscription)
import { useBlocActions } from '@blac/react';
function ActionsOnly() {
const bloc = useBlocActions(CounterBloc);
// Never re-renders due to state changes
return <button onClick={bloc.increment}>+</button>;
}
Instance Management
Static Methods
| Method | Purpose | Ref Count |
|---|---|---|
.resolve(id?, ...args) |
Get/create with ownership | Increments |
.get(id?) |
Borrow existing (throws if missing) | No change |
.getSafe(id?) |
Borrow existing (returns error) | No change |
.connect(id?, ...args) |
Get/create for B2B communication | No change |
.release(id?, force?) |
Release reference | Decrements |
Bloc-to-Bloc Communication
In event handlers or methods (borrowing):
class UserBloc extends Cubit<UserState> {
loadProfile = () => {
// Borrow - no memory leak, no cleanup needed
const analytics = AnalyticsCubit.get();
analytics.trackEvent('profile_loaded');
};
}
In getters (automatic tracking):
class CartCubit extends Cubit<CartState> {
get totalWithShipping(): number {
const shipping = ShippingCubit.get(); // Auto-tracked!
return this.itemTotal + shipping.state.cost;
}
}
System Events
class MyBloc extends Cubit<State> {
constructor() {
super(initialState);
this.onSystemEvent('stateChanged', ({ state, previousState }) => {
console.log('State changed');
});
this.onSystemEvent('propsUpdated', ({ props, previousProps }) => {
console.log('Props updated');
});
this.onSystemEvent('dispose', () => {
console.log('Disposing - cleanup here');
});
}
}
Best Practices
DO:
- Use arrow functions for methods (correct
thisbinding in React) - Keep state immutable (create new objects/arrays)
- Use
patch()for simple field updates,update()for nested changes - Use
useBlocActionswhen only calling methods (no state reading) - Use getters for computed values (cached per render cycle)
- Use
.get()instead of.resolve()in bloc-to-bloc communication
DON'T:
- Mutate state directly (
this.state.todos.push(...)) - Destructure entire state when you only need specific properties
- Use
.resolve()in getters (causes memory leaks) - Use
patch()for nested object updates (shallow merge only)
Common Patterns
Form State (Isolated)
@blac({ isolated: true })
class FormBloc extends Cubit<FormState> {
constructor() {
super({ values: {}, errors: {}, isSubmitting: false });
}
setField = (field: string, value: string) => {
this.update(state => ({
...state,
values: { ...state.values, [field]: value },
errors: { ...state.errors, [field]: '' }
}));
};
}
Async Data Fetching
class UserBloc extends Cubit<DataState<User>> {
constructor() {
super({ data: null, isLoading: false, error: null });
}
fetchUser = async (id: string) => {
this.patch({ isLoading: true, error: null });
try {
const data = await api.getUser(id);
this.patch({ data, isLoading: false });
} catch (error) {
this.patch({ error: error.message, isLoading: false });
}
};
}
Shared Singleton Service
@blac({ keepAlive: true })
class AnalyticsService extends Cubit<AnalyticsState> {
trackEvent = (name: string, data: Record<string, any>) => {
// Other blocs can safely call: AnalyticsService.get().trackEvent(...)
};
}
Troubleshooting
Component not re-rendering?
- Access state properties during render, not before
- Check you're using
useBloc, not just readingbloc.state
Too many re-renders?
- Access only the properties you need
- Don't destructure entire state object
- Consider
useBlocActionsfor action-only components
Shared state not working?
- Check if bloc is marked
@blac({ isolated: true }) - Verify same
instanceIdif using custom IDs
Performance Optimization
Optimal Property Access
// ✅ OPTIMAL: Access only what you render
function UserCard() {
const [user] = useBloc(UserBloc);
return <h2>{user.name}</h2>; // Only tracks 'name'
}
// ❌ AVOID: Destructuring tracks everything
const { name, email, bio } = user; // Re-renders on ANY change
Component Splitting
// ✅ Split into granular components
function TodoApp() {
return (
<>
<TodoCount /> {/* Only re-renders on count change */}
<TodoList /> {/* Only re-renders on todos change */}
<TodoActions /> {/* Never re-renders (uses useBlocActions) */}
</>
);
}
function TodoActions() {
const cubit = useBlocActions(TodoCubit); // No state subscription
return <button onClick={cubit.addTodo}>Add</button>;
}
Performance Summary
| Pattern | Re-renders | Use When |
|---|---|---|
| Auto-tracking (default) | On tracked property change | Most cases |
useBlocActions |
Never | Action-only components |
Manual dependencies |
On dependency change | Known fixed dependencies |
| Getters | On computed value change | Derived/computed state |
Common Mistakes
- Destructuring state - Tracks all destructured properties
- Spreading props -
<Child {...state} />defeats tracking - Using
.resolve()in methods - Use.get()for bloc-to-bloc calls - Not using
useBlocActions- Creates unnecessary subscriptions
For complete API reference, see REFERENCE.md.