BlaC requires React 18+ and TypeScript is strongly recommended.
Recommended tsconfig.json
BlaC works with a standard strict React setup. The @blac() decorator (used for keepAlive, static key, and other options) works as either a legacy (experimentalDecorators) or a TC39/stage-3 decorator — enable decorator support in your tsconfig to use the @blac(...) syntax. Or skip decorators entirely with the functional form — blac({ ... })(class extends Cubit { ... }) — which needs no extra compiler flags.
{
"compilerOptions": {
"target": "ESNext",
"jsx": "react-jsx",
"strict": true,
"useDefineForClassFields": true,
"experimentalDecorators": true, // only if you use @blac(...) decorator syntax
Cubit<S> is a StateContainer<S> with emit / patch exposed as
public mutation surface. Today it adds nothing structurally beyond
StateContainer — both are inherited from the underlying
StructuralContainer<S>. Kept as a real class (not a type alias) because
downstream code does instance instanceof Cubit checks.
The class body is intentionally empty: a no-op emit override would
still go through applyState, and patch is inherited from
StructuralContainer (path-diffed, microtask-flushed). A caller that
wants "skip if no real change" patch semantics can wrap patch
themselves or call emit after a manual equality check.
Cubit<S> is a StateContainer<S> with emit / patch exposed as
public mutation surface. Today it adds nothing structurally beyond
StateContainer — both are inherited from the underlying
StructuralContainer<S>. Kept as a real class (not a type alias) because
downstream code does instance instanceof Cubit checks.
The class body is intentionally empty: a no-op emit override would
still go through applyState, and patch is inherited from
StructuralContainer (path-diffed, microtask-flushed). A caller that
wants "skip if no real change" patch semantics can wrap patch
themselves or call emit after a manual equality check.
Override of StructuralContainer.patch that routes through the
StateContainer concerns: disposed guard, dev-only emit-rate check,
_changedWhileHydrating flag, pending-change capture (so stateChanged
system events see the merged prev/next), and the registry-level
stateChanged notification. We still call
super.patch so path-marking semantics (the whole point of patch) are
preserved.
// patch deep-merges, so we only mention the key we change.
this.patch({ items: this.state.items.filter((_, i)=> i !== index) });
};
getisEmpty() {
returnthis.state.items.length===0;
}
}
Notice the two write styles: addTodo uses update and lists both keys (replacing the whole state), while removeTodo uses patch and mentions onlyitems (merging into the rest). Both are correct — the difference is exactly the replace-vs-merge rule from Step 1.
Getters like isEmpty derive a value on every read, so they can never drift from items. When a component reads todo.isEmpty during render, auto-tracking records the getter’s underlying this.state.items.length read through the proxy. Use select only when you want the getter’s return value to be the explicit re-render boundary. The full rule is in Dependency Tracking. For async work (loading flags, fetches, request guards), see Patterns & Recipes.
An async action is just a method that awaits and emits as it goes. Model the lifecycle as a status union so the view can render loading and error states, and use a request-id guard so a slow response can never overwrite a newer one.
Cubit<S> is a StateContainer<S> with emit / patch exposed as
public mutation surface. Today it adds nothing structurally beyond
StateContainer — both are inherited from the underlying
StructuralContainer<S>. Kept as a real class (not a type alias) because
downstream code does instance instanceof Cubit checks.
The class body is intentionally empty: a no-op emit override would
still go through applyState, and patch is inherited from
StructuralContainer (path-diffed, microtask-flushed). A caller that
wants "skip if no real change" patch semantics can wrap patch
themselves or call emit after a manual equality check.
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(
function(localvar)e: unknown
e) });
}
};
}
The view switches on state.status and TypeScript narrows each branch. Derived loading flags, the loadable surface, cancellation with AbortController, and why BlaC does not use React Suspense are all in the Async guide.
The registry checks if an instance of CounterCubit already exists
If not, it creates one and stores it. If yes, it returns the existing one
A ref count is incremented (tracking how many components use this instance)
The hook subscribes to state changes using auto-tracking — a Proxy wraps the state and records which properties your render function accesses
On re-render, only changes to those specific properties trigger an update
When the component unmounts, the ref count decrements. At zero, the instance is disposed
Each of these steps has a “why” worth understanding once your app grows — why a proxy beats selectors, why disposal is automatic, why updates batch on a microtask. That deep version lives in the Mental Model.