Core Concepts
This is the quick conceptual tour — one screen per idea, enough to start making good decisions about how to structure your state. For the deep “why it works this way” (the reactivity model, the per-consumer tracker, batching and ref-counting rationale, and honest comparisons to other libraries), read the Mental Model. For one-line definitions of every term, see the Glossary.
State Containers
Section titled “State Containers”A state container is a class that holds a typed state value and notifies listeners when it changes.
Think of it like a mini-store scoped to one concern. An AuthCubit holds auth state. A CartCubit holds cart state. Each is a self-contained unit with its own state type, methods, and lifecycle.
Cubit is the concrete class you’ll extend. It gives you three ways to change state:
class AuthCubit extends Cubit<{ user: User | null; loading: boolean }> { constructor() { super({ user: null, loading: false }); // initial state }
login = async (credentials: Credentials) => { this.patch({ loading: true }); // merge partial changes const user = await api.login(credentials); this.emit({ user, loading: false }); // replace entire state };
logout = () => { this.update((s) => ({ ...s, user: null })); // derive from current };}State containers are framework-agnostic — they work without React. You can instantiate them in a test, call methods, and assert on state directly. No DOM, no hooks, no providers needed.
Registry
Section titled “Registry”The registry is a global singleton that manages state container instances. When you call useBloc(CounterCubit) in two different components, they both get the same CounterCubit instance. The registry makes this happen.
It maps each class (and optional instance key) to a single instance, plus a ref count tracking how many consumers are using it:
Registry├── CounterCubit (default) → instance, refCount: 2 ← two components├── AuthCubit (default) → instance, refCount: 1└── EditorCubit ("doc-42") → instance, refCount: 3Ref counting
Section titled “Ref counting”Every useBloc call increments the ref count on mount and decrements it on unmount. When the count hits zero, the instance is automatically disposed — its resources are cleaned up and it’s removed from the registry. This means you don’t need to worry about memory leaks from forgotten state containers.
If you want an instance to survive even when nothing is using it, mark it with @blac({ keepAlive: true }).
Registry functions
Section titled “Registry functions”In React, useBloc handles the registry for you. Outside React (tests, scripts, server-side), you interact with it directly. The headline verbs:
| Function | Creates? | Ref count | Use when |
|---|---|---|---|
acquire(Class) | Yes | +1 | You own this reference (must release later) |
ensure(Class) | Yes | No change | You need the instance but don’t own a reference |
borrow(Class) | No | No change | Instance must already exist (throws if not) |
release(Class) | No | -1 | Done with your reference |
This is the quick version. The complete function table (including borrowSafe, getRefCount, clear, and the keying rules) lives in Instance Management.
Instance Modes
Section titled “Instance Modes”Shared (default)
Section titled “Shared (default)”All calls to useBloc(CounterCubit) return the same instance. This is the common case for app-wide state like auth, theme, or cart.
Put the distinguishing value in args (and select it with static key) to get a distinct instance per key. Different args resolve to different instances.
useBloc(EditorCubit, { args: { docId: 'doc-42' } });Keep alive
Section titled “Keep alive”With @blac({ keepAlive: true }), the instance survives even when all components using it unmount. It persists for the lifetime of the app.
Inputs: args and deps
Section titled “Inputs: args and deps”Components rarely need a blank container — they need one seeded with data. BlaC keeps that data in two separate lanes so a shared instance with many consumers never races:
args— serializable data that identifies an instance (e.g. a document id). Same args resolve to the same instance; identity is derived from args alone.deps— non-serializable handles (callbacks, services) injected per consumer, never used for identity.
This is just the teaser; the full model, the precedence rules, and the failure modes live in Inputs.
Dependency Tracking
Section titled “Dependency Tracking”This is BlaC’s key performance feature — the quick tour here, with the full mechanism and its edge cases in Dependency Tracking. When you call useBloc, the returned state is wrapped in a Proxy that records which properties your component actually reads during render:
function UserName() { const [state] = useBloc(UserCubit); return <span>{state.name}</span>; // only 'name' is tracked}If state.email changes but state.name doesn’t, this component won’t re-render. This happens automatically — no selectors, no useMemo, no React.memo.
See it live: CountReader tracks state.count; LabelReader tracks state.label. Each component’s render badge increments only when its own field changes.
Two components share one bloc. Each reads a different field. Change one — only its reader re-renders.
reads: state.count
reads: state.label
The tracking also works for:
- Nested properties:
state.user.profile.name(records the leaf path) - Array access:
state.items.length,state.items[0]
Getter reads participate too: the bloc returned by useBloc is proxied, so reading bloc.total during render records the this.state paths the getter touches. Use select when you want a getter’s return value, rather than its source paths, to decide re-renders. See Dependency Tracking.
There are two tracking modes, chosen by whether you pass a select option — there is no separate on/off flag:
| Mode | How | Best for |
|---|---|---|
| Auto-tracking (default) | Proxy records property access during render | Most components |
select (manual) | You return an array; re-render when it changes per-index | Complex conditions or computed values |
A component that only calls methods and never displays state needs no special option: do not read from state, and auto-tracking records nothing to wake it. See Dependency Tracking for the full story, including the conditional-read caveat.
Plugins
Section titled “Plugins”Plugins observe lifecycle events across all state containers. They receive callbacks for instance creation, state changes, and disposal.
const plugin: BlacPlugin = { name: 'my-plugin', version: '1.0.0', onStateChange(ctx, prev, next, paths) { ... },};
getPluginManager().install(plugin);Official plugins: Logging, DevTools, Persistence.
Glossary
Section titled “Glossary”Every term used above — StateContainer, Cubit, registry, ref counting, auto-tracking, args/deps, hydration, depend(), and the rest — has a one-line definition in the Glossary, each linking to its deep page.
What’s next?
Section titled “What’s next?”- Mental Model — The deep “why” behind everything on this page
- Inputs — args, deps, and instance identity in full
- Patterns & Recipes — Common patterns for structuring your app
- Cubit — Full Cubit API reference
- useBloc — Hook options and tracking modes
See also
Section titled “See also”- Mental Model — the deep version of this tour
- Glossary — definitions for every term here
- Instance Management — the complete registry function reference
- Dependency Tracking — auto-tracking in depth