BlaC is written in TypeScript and assumes you are too. Almost everything you need — state shape, action signatures, the args a consumer must pass — flows from a single class declaration, so most of this page is about reading the inference rather than writing annotations.
This is the discoverable, example-driven tour. For the exhaustive list of exported type utilities (ExtractState, ExtractArgs, ExtractDeps, InstanceReadonlyState, and friends), see Core Types.
BlaC works with a standard strict React setup. The defaults that matter:
{
"compilerOptions":{
"target":"ESNext",
"jsx":"react-jsx",
"strict":true,
"useDefineForClassFields":true,
"experimentalDecorators":true,// only if you use @blac(...) decorator syntax
},
}
strict: true is the assumption behind every inference example below — strictNullChecks in particular is what makes this.args correctly Args | undefined and what forces you to narrow discriminated unions. Without it the examples still compile, but the safety they demonstrate is gone.
Decorators are optional. The @blac(...) configuration decorator works as either a legacy (experimentalDecorators) decorator or a TC39/stage-3 decorator — pick whichever your toolchain emits. If you’d rather not touch decorator flags at all, the functional form needs none:
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.
Cubit<{
user: string |null
user:string|null}>{
constructor(){
super({
user: string |null
user:null});
}
},
);
blac(opts)(class) is a plain higher-order function — no compiler flag, no syntax proposal. The decorator form is sugar over exactly this. See Configuration for the full options union (keepAlive, equality, excludeFromDevTools, key).
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.
Declaring S is the only annotation you need — state, emit, update, and patch all specialize from it. Hover any of these and you’ll see the concrete type, not any:
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.
Shallow-or-deep patch: accepts a DeepPartial<S> so nested object
branches type-check without casts. deepMerge walks plain-object branches
and treats class instances, arrays, Date, Map, Set, etc. as atomic leaves.
Dirty paths are derived from the patch's shape but value-filtered: a
path is marked only if its value actually changed. Plain-object branches are
handled precisely by changedPathsFromPatch. For atomic-leaf replacements
(arrays, class instances, etc.) where changedPathsFromPatch would emit a
coarse ancestor-watch mark, _refineAncestorMarks replaces it with precise
skeleton-child diffs so per-index consumers only wake when their specific
value changed.
@see ― emit for the StateContainer concerns layered onto super.patch.
patch({
count?: number |undefined
count:2});// patch takes DeepPartial<S>
}
}
emit and the value update’s callback returns both require the completeS — omit a key and it’s a type error, which is the compiler enforcing the replace-not-merge rule. patch accepts a DeepPartial<S>, so partial objects are legal there and only there:
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.
count:1});// missing `label` — emit replaces, so this is an error
Error ts(2345) ― Argument of type '{ count: number; }' is not assignable to parameter of type 'CounterState'.
Property 'label' is missing in type '{ count: number; }' but required in type 'CounterState'.
Anything computed from state belongs in a getter. Getters infer their return type, can’t drift from the state they read, and require no extra type plumbing:
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.
Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
@param ― callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
@param ― initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
reduce((
sum: number
sum,
i:{
price: number;
qty: number;
}
i)=>
sum: number
sum+
i:{
price: number;
qty: number;
}
i.
price: number
price*
i:{
price: number;
qty: number;
}
i.
qty: number
qty,0);
}
get
CartCubit.isEmpty: boolean
isEmpty(){
// return type inferred as boolean — no annotation needed
The single most useful TypeScript pattern in BlaC is a discriminated-union state. Model a request as a status tag and the compiler will force you to handle every case and forbid you from reading a field before it exists.
Declare the union as the state type. Each variant carries only the data that’s valid in that state:
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.
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(
function(local var)e: unknown
e)});
}
};
}
declarefunction
functionfetchUser(id:string):Promise<User>
fetchUser(
id: string
id:string):
interfacePromise<T>
Represents the completion of an asynchronous operation
Promise<
interfaceUser
User>;
Because emit wants the full UserState, you can’t emit { status: 'success' } without a user — the variant’s required fields are checked at the emit site:
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.
Error ts(2345) ― Argument of type '{ status: "success"; }' is not assignable to parameter of type 'UserState'.
Property 'user' is missing in type '{ status: "success"; }' but required in type '{ status: "success"; user: { id: string; }; }'.
The payoff is on the read side. Switch on state.status and inside each branch TypeScript narrows the union — state.user exists only in the success arm, state.error only in error. Here the consumer is a plain function so the inference is visible; in a component the same state comes out of useBloc:
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.
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.
Per-mount private instance:
const id =useId();
const[state, bloc]=useBloc(MyBloc,{ args:{ _id: id }});
@typeParam ― T - The state container constructor type (inferred from BlocClass)
@param ― BlocClass - The state container class to connect to
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.
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.
Per-mount private instance:
const id =useId();
const[state, bloc]=useBloc(MyBloc,{ args:{ _id: id }});
@typeParam ― T - The state container constructor type (inferred from BlocClass)
@param ― BlocClass - The state container class to connect to
// no narrowing yet — `user` doesn't exist on the `loading` arm
return
const state:{
readonly status:"loading";
}|{
readonly status:"success";
readonly user:{
readonly name:string;
};
}
state.user.
any
name;
Error ts(2339) ― Property 'user' does not exist on type '{ readonly status: "loading"; } | { readonly status: "success"; readonly user: { readonly name: string; }; }'.
Property 'user' does not exist on type '{ readonly status: "loading"; }'.
}
Narrowing works identically inside a getter — derive a flag once and read it everywhere:
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.
select lets a consumer use an explicit dependency array instead of auto-tracked reads. The component re-renders only when one of those values changes (compared per-index with Object.is). Its signature is:
Extract the state type from a StateContainer, deeply readonly.
State is owned by the container: consumers read it and mutate only through
emit/patch, so nested writes are a compile error rather than a silent
desync from the change-tracking channel.
Both arguments are fully inferred from the bloc you pass to useBloc — state is the readonly state, bloc is the readonly instance (so getters are reachable). The return type is unknown[], an array of whatever values gate the re-render:
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.
Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
@param ― callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
@param ― initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
reduce((
s: number
s,
i:{
price: number;
qty: number;
}
i)=>
s: number
s+
i:{
price: number;
qty: number;
}
i.
price: number
price*
i:{
price: number;
qty: number;
}
i.
qty: number
qty,0);
}
}
function
functioncartTotal():number
cartTotal():number{
// re-renders only when `total` or item count changes
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.
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.
Per-mount private instance:
const id =useId();
const[state, bloc]=useBloc(MyBloc,{ args:{ _id: id }});
@typeParam ― T - The state container constructor type (inferred from BlocClass)
@param ― BlocClass - The state container class to connect to
Per-consumer re-render selector. When provided, the hook re-renders
only when the returned array's elements change (Object.is per index).
When omitted, auto-tracking is used: any state path read during the
render is observed, and the hook re-renders when any of those paths
change.
Re-run on every render, so it may close over props; an inline function
is fine.
select:(
state:{
readonly items: readonly {
readonly price: number;
readonly qty: number;
}[];
readonly coupon: string |null;
}
state, bloc)=>[bloc.
CartCubit.total: number
total,
state:{
readonly items: readonly {
readonly price: number;
readonly qty: number;
}[];
readonly coupon: string |null;
}
state.
items: readonly {
readonly price: number;
readonly qty: number;
}[]
items.
ReadonlyArray<{ readonly price: number; readonly qty: number;}>.length: number
Gets the length of the array. This is a number one higher than the highest element defined in an array.
state is Readonly, so select can’t accidentally mutate it; that’s also why reading a getter here (bloc.total) is the supported way to make a derived value drive re-renders. See useBloc.
When a bloc declares an Args type, args in useBloc is optional — you may omit it and the hook will inherit args from a <BlocProvider> ancestor, or fall back to the default instance key. When the bloc’s Args is the default void, passing args is forbidden. The type system enforces both directions through a conditional option type:
ExtractArgs<T> pulls the second generic off the class. If it’s void, the option becomes { args?: never } — present-but-forbidden. Otherwise it’s { args?: Args } — optional and typed.
A void-args bloc rejects args (the option’s type there is never):
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.
Cubit<{
count: number
count:number}>{
constructor(){
super({
count: number
count:0});
}
}
function
functioncount():number
count():number{
// CounterCubit has no Args → passing `args` is a type error
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.
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.
Per-mount private instance:
const id =useId();
const[state, bloc]=useBloc(MyBloc,{ args:{ _id: id }});
@typeParam ― T - The state container constructor type (inferred from BlocClass)
@param ― BlocClass - The state container class to connect to
Error ts(2322) ― Type '{ foo: number; }' is not assignable to type 'undefined'.
return
const state:{
readonly count:number;
}
state.
count: number
count;
}
A bloc that declares Args accepts it as optional — omitting it inherits args from a <BlocProvider> ancestor, else the default key is used. When you do pass args, it is type-checked against the declared shape:
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.
Cubit<
interfaceUserState
UserState,{
userId: string
userId:string}>{
constructor(){
super({
UserState.name: string
name:''});
}
protected
UserCubit.init(args: {
userId: string;
}): void
Called once after construction with the args passed at acquire time, before the first
state snapshot is read by any consumer. Override to seed args-derived state (via
this.emit(...)) or kick off loads.
init(
args:{
userId: string;
}
args:{
userId: string
userId:string}){
void
args:{
userId: string;
}
args.
userId: string
userId;
}
}
function
functionuserName():string
userName():string{
// args is optional — omit to inherit from BlocProvider, or pass explicitly:
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.
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.
Per-mount private instance:
const id =useId();
const[state, bloc]=useBloc(MyBloc,{ args:{ _id: id }});
@typeParam ― T - The state container constructor type (inferred from BlocClass)
@param ― BlocClass - The state container class to connect to
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.
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.
Per-mount private instance:
const id =useId();
const[state, bloc]=useBloc(MyBloc,{ args:{ _id: id }});
@typeParam ― T - The state container constructor type (inferred from BlocClass)
@param ― BlocClass - The state container class to connect to
Inside the bloc, the args getter is Args | undefined (it’s unset until the instance is acquired), so reach for the value init(args) hands you when you need it non-optionally:
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.
Cubit<{
name: string
name:string},{
userId: string
userId:string}>{
constructor(){
super({
name: string
name:''});
}
protected
UserCubit.init(args: {
userId: string;
}): void
Called once after construction with the args passed at acquire time, before the first
state snapshot is read by any consumer. Override to seed args-derived state (via
this.emit(...)) or kick off loads.
init(
args:{
userId: string;
}
args:{
userId: string
userId:string}){
// `args` here is the non-optional declared shape; init just seeds state
}
// Side effects belong in onActivate, not init.
protected
UserCubit.onActivate(signal: AbortSignal): void
Called when this instance goes from unowned to owned (first ref or first
depend() dependent). Override to start work that should only run while
something is actually using the instance — fetches, timers, listeners.
signal aborts on the matching onDeactivate and on dispose, so
async work started here can be cancelled without extra bookkeeping.
Runs before the first useBlocDeps slice is applied, so this.deps is
empty on first activation — use onDepsChanged for deps-driven work.
onActivate(
signal: AbortSignal
signal:
interfaceAbortSignal
The AbortSignal interface represents a signal object that allows you to communicate with an asynchronous operation (such as a fetch request) and abort it if required via an AbortController object.
Wrapping useBloc in a domain hook is the idiomatic way to give a feature a named, typed entry point. Inference is preserved end to end as long as you don’t widen the return — let it flow:
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.
Shallow-or-deep patch: accepts a DeepPartial<S> so nested object
branches type-check without casts. deepMerge walks plain-object branches
and treats class instances, arrays, Date, Map, Set, etc. as atomic leaves.
Dirty paths are derived from the patch's shape but value-filtered: a
path is marked only if its value actually changed. Plain-object branches are
handled precisely by changedPathsFromPatch. For atomic-leaf replacements
(arrays, class instances, etc.) where changedPathsFromPatch would emit a
coarse ancestor-watch mark, _refineAncestorMarks replaces it with precise
skeleton-child diffs so per-index consumers only wake when their specific
value changed.
@see ― emit for the StateContainer concerns layered onto super.patch.
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.
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.
Per-mount private instance:
const id =useId();
const[state, bloc]=useBloc(MyBloc,{ args:{ _id: id }});
@typeParam ― T - The state container constructor type (inferred from BlocClass)
@param ― BlocClass - The state container class to connect to
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.
Cubit<
interfaceUserState
UserState,{
userId: string
userId:string}>{
constructor(){
super({
UserState.name: string
name:''});
}
protected
UserCubit.init(args: {
userId: string;
}): void
Called once after construction with the args passed at acquire time, before the first
state snapshot is read by any consumer. Override to seed args-derived state (via
this.emit(...)) or kick off loads.
init(
args:{
userId: string;
}
args:{
userId: string
userId:string}){
void
args:{
userId: string;
}
args.
userId: string
userId;
}
}
// the hook's signature documents what the feature needs
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.
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.
Per-mount private instance:
const id =useId();
const[state, bloc]=useBloc(MyBloc,{ args:{ _id: id }});
@typeParam ― T - The state container constructor type (inferred from BlocClass)
@param ― BlocClass - The state container class to connect to
If you do want to annotate the return — for a public package API, say — derive it from the bloc rather than restating the shape. ExtractState and InstanceReadonlyState are exported from @blac/core:
Extract the state type from a StateContainer, deeply readonly.
State is owned by the container: consumers read it and mutate only through
emit/patch, so nested writes are a compile error rather than a silent
desync from the change-tracking channel.
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.
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.
Per-mount private instance:
const id =useId();
const[state, bloc]=useBloc(MyBloc,{ args:{ _id: id }});
@typeParam ― T - The state container constructor type (inferred from BlocClass)
@param ― BlocClass - The state container class to connect to