BlaC (Business Logic Components) is a TypeScript state management library for React. It separates business logic into class-based state containers — Cubits — that are type-safe, testable, and automatically optimized for minimal re-renders.
Cubit<S> is a StateContainer<S> that exposes mutation publicly.
That is the whole difference, and it is a real one: on StateContaineremit / patch / update are protected, so a container mutates itself
from its own methods and callers go through the API it chooses to publish.
Cubit re-declares the three as public for the cases where a caller
legitimately drives state from outside — test helpers, devtools
time-travel, benchmarks.
Reach for StateContainer when business logic should live in the class,
and Cubit when the caller owns the transitions.
React hook that connects a component to a state container with automatic
re-render on state changes.
Two tracking modes:
Auto-tracking (default): the returned state value is a proxy that
records read paths during render. The component re-renders when any
recorded path changes. Backed by @dirtytalk/structural's
trackRender + the container's path-scoped DirtyChannel.
Manual select: pass options.select to opt out of auto-tracking.
The hook re-renders only when the returned array's elements change
(per-index Object.is).
Lifecycle:
The bloc is acquired from the registry on mount and released on
unmount. The instance key is derived from options.args (own args),
then the surrounding
BlocProvider
context args for this bloc,
then the default key (no args).
options.onMount fires after the bloc is acquired; options.onUnmount
fires before the registry releases its ref, so the bloc is still
alive when the callback runs.
Cubit<S> is a StateContainer<S> that exposes mutation publicly.
That is the whole difference, and it is a real one: on StateContaineremit / patch / update are protected, so a container mutates itself
from its own methods and callers go through the API it chooses to publish.
Cubit re-declares the three as public for the cases where a caller
legitimately drives state from outside — test helpers, devtools
time-travel, benchmarks.
Reach for StateContainer when business logic should live in the class,
and Cubit when the caller owns the transitions.
React hook that connects a component to a state container with automatic
re-render on state changes.
Two tracking modes:
Auto-tracking (default): the returned state value is a proxy that
records read paths during render. The component re-renders when any
recorded path changes. Backed by @dirtytalk/structural's
trackRender + the container's path-scoped DirtyChannel.
Manual select: pass options.select to opt out of auto-tracking.
The hook re-renders only when the returned array's elements change
(per-index Object.is).
Lifecycle:
The bloc is acquired from the registry on mount and released on
unmount. The instance key is derived from options.args (own args),
then the surrounding
BlocProvider
context args for this bloc,
then the default key (no args).
options.onMount fires after the bloc is acquired; options.onUnmount
fires before the registry releases its ref, so the bloc is still
alive when the callback runs.
No store setup, no provider, no reducer, no selector. The class holds the logic; the hook connects it; re-renders are tracked automatically.
That snippet isn’t a screenshot — here it is running, the real @blac/react hook driving a real Cubit. The badge counts how many times the component has actually rendered:
Most state management libraries force you to choose between simplicity and power. Simple hooks-based solutions scatter logic across components. Powerful libraries require boilerplate, providers, and context wrappers.
BlaC takes a different approach:
State logic lives in classes, not components. Define your state shape and mutations in a Cubit class. Components just read state and call methods.
No providers or context wrappers. Import your class, call useBloc(MyClass), and you’re connected. The registry handles instance creation and sharing automatically.
Re-renders are precise by default. Auto-tracking proxies detect which state properties your component reads during render. Only changes to those properties trigger re-renders.
Lifecycle is declarative. Instances are shared by default. Use args (with a static key) for named per-component instances or @blac({ keepAlive: true }) for persistent singletons.
Built for TypeScript. State types flow from your class definition through the hook return value with zero type annotations needed.
Two components share one bloc — each reads a different field. Increment the count or edit the label: only the component reading the changed field re-renders.
Re-render isolation — live demo
Two components share one bloc. Each reads a different field. Change one — only its reader re-renders.
Core (@blac/core) provides state containers, a global registry with ref counting, a plugin system, and utilities like watch. Proxy-based dependency tracking is built in — no separate adapter package is needed.
React (@blac/react) provides the useBloc hook. It subscribes each component to the bloc’s path-scoped channel and re-renders through React’s normal update path when a tracked read path changes. The optional BlocProvider shown above scopes default args to a subtree — most apps never need it (see useBloc for when it helps).
Under the hood: the DirtyTalk engine family
The “what changed, who cares, when do we tell them” machinery — path-based dirty tracking, the render-time proxy, microtask-batched flushing — lives in a lower-level, framework-agnostic family of packages called DirtyTalk. BlaC’s StateContainer extends @dirtytalk/structural’s container; you never need to touch it directly, but it’s there if you want to understand the foundation.