Skip to content

Core Concepts

Names for the pieces, one line each, linked to where each is specified in full. For the why behind these choices, read Mental Model. For one-line definitions of every term, see the Glossary.

A state container is a class holding a typed state value that notifies listeners on change. Cubit is the class you extend — it gives you emit, patch, and update to change state:

class AuthCubit extends Cubit<{ user: User | null; loading: boolean }> {
constructor() {
super({ user: null, loading: false });
}
login = async (credentials: Credentials) => {
this.patch({ loading: true });
const user = await api.login(credentials);
this.emit({ user, loading: false });
};
}

It’s framework-agnostic — construct it, call methods, assert on state, no React needed. Full reference: Cubit.

A global singleton maps each class (plus an optional args-derived key) to one shared instance with a ref count. useBloc(CounterCubit) in two components returns the same instance. Refs increment on mount, decrement on unmount; at zero, the instance disposes automatically unless marked @blac({ keepAlive: true }). Distinct args key a distinct instance (a “named” instance) instead of the shared default.

Full function table (acquire/ensure/borrow/release/ref-count queries) and keying rules: Instance Management.

Components rarely need a blank container — they need one seeded with data. BlaC keeps that data in two lanes: args — serializable data that identifies an instance — and deps — non-serializable per-consumer handles, never used for identity.

Full model, precedence rules, and failure modes: Inputs.

BlaC’s key performance feature: the state returned by useBloc is a Proxy that records which properties your component reads during render, and only changes to those properties trigger a re-render — no selectors, no useMemo.

Re-render isolation — live demo

Two components share one bloc. Each reads a different field. Change one — only its reader re-renders.

CountReaderrenders1

reads: state.count

0
LabelReaderrenders1

reads: state.label

CountReader above tracks only state.count; LabelReader tracks only state.label. Full mechanism, edge cases, and the select opt-out: Dependency Tracking.

Plugins observe lifecycle events — creation, state changes, disposal — across every state container:

getPluginManager().install({
name: 'my-plugin',
version: '1.0.0',
onStateChange(ctx, prev, next, paths) { ... },
});

Official plugins: Logging, DevTools, Persistence.