Develop with BlaC state management library for React. Use when creating Cubits, using useBloc/useBlocActions hooks, managing state containers, or implementing inter-bloc communication patterns.
BlaC is a TypeScript state management library with React integration using proxy-based dependency tracking for optimal re-renders.
pnpm add @blac/core @blac/react
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)@blac() Decoratorimport { 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.
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]
const [state, bloc] = useBloc(MyBloc, {
instanceId: 'main', // Custom instance ID for shared blocs
dependencies: (state, bloc) => [state.count], // Manual dependency tracking
autoTrack: false, // Disable automatic tracking
onMount: (bloc) => bloc.fetchData(), // Lifecycle callbacks
onUnmount: (bloc) => bloc.cleanup(),
});
import { useBloc } from '@blac/react';
function ActionsOnly() {
const [, bloc] = useBloc(CounterBloc);
// Avoid reading state to keep renders minimal
return <button onClick={bloc.increment}>+</button>;
}
| Function | Purpose | Ref Count |
|---|---|---|
acquire(BlocClass, instanceKey?) |
Get/create with ownership | Increments |
release(BlocClass, instanceKey?) |
Release ownership | Decrements |
ensure(BlocClass, instanceKey?) |
Get/create without ownership | No change |
borrow(BlocClass, instanceKey?) |
Borrow existing (throws if missing) | No change |
borrowSafe(BlocClass, instanceKey?) |
Borrow existing (returns error) | No change |
In event handlers or methods (no ownership):
import { ensure } from '@blac/core';
class UserBloc extends Cubit<UserState> {
loadProfile = () => {
// No ownership - no cleanup needed
const analytics = ensure(AnalyticsCubit);
analytics.trackEvent('profile_loaded');
};
}
In getters (automatic tracking):
import { ensure } from '@blac/core';
class CartCubit extends Cubit<CartState> {
get totalWithShipping(): number {
const shipping = ensure(ShippingCubit); // Auto-tracked!
return this.itemTotal + shipping.state.cost;
}
}
class MyBloc extends Cubit<State> {
constructor() {
super(initialState);
this.onSystemEvent('stateChanged', ({ state, previousState }) => {
console.log('State changed');
});
this.onSystemEvent('dispose', () => {
console.log('Disposing - cleanup here');
});
}
}
this binding in React)patch() for simple field updates, update() for nested changesuseBloc and avoid reading stateensure() or borrow() for bloc-to-bloc communicationthis.state.todos.push(...))acquire() without a matching release()patch() for nested object updates (shallow merge only)@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]: '' }
}));
};
}
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 });
}
};
}
@blac({ keepAlive: true })
class AnalyticsService extends Cubit<AnalyticsState> {
trackEvent = (name: string, data: Record<string, any>) => {
// Other blocs can safely call: ensure(AnalyticsService).trackEvent(...)
};
}
Component not re-rendering?
useBloc, not just reading bloc.stateToo many re-renders?
dependencies or autoTrack: false for coarse updatesShared state not working?
@blac({ isolated: true })instanceId if using custom IDs// ✅ 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
// ✅ Split into granular components
function TodoApp() {
return (
<>
<TodoCount /> {/* Only re-renders on count change */}
<TodoList /> {/* Only re-renders on todos change */}
<TodoActions /> {/* Avoids state reads */}
</>
);
}
function TodoActions() {
const [, cubit] = useBloc(TodoCubit); // Avoid reading state
return <button onClick={cubit.addTodo}>Add</button>;
}
| Pattern | Re-renders | Use When |
|---|---|---|
| Auto-tracking (default) | On tracked property change | Most cases |
| Action-only (no state reads) | Never | Buttons/handlers |
Manual dependencies |
On dependency change | Known fixed dependencies |
| Getters | On computed value change | Derived/computed state |
<Child {...state} /> defeats trackingacquire() without release() - Use ensure()/borrow() for one-off accessFor complete API reference, see REFERENCE.md.