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> 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<{
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> 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.
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> 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({
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> 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.
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> 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.
}[])=> number, initialValue: number): number (+2 overloads)
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
returnthis.
StructuralContainer<CartState>.state: CartState
state.
CartState.items: {
price: number;
qty: number;
}[]
items.
Array<{ price: number; qty: number; }>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.
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> 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) });
}
};
}
declarefunction
functionfetchUser(id:string):Promise<User>
fetchUser(
id: string
id:string):
interface Promise<T>
Represents the completion of an asynchronous operation
Promise<
interface User
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> 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.
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. 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.
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 narrowing yet — `user` doesn't exist on the `loading` arm
return
const state:Readonly<UserState>
state.user.
any
name;
Error ts(2339) ― Property 'user' does not exist on type 'Readonly<UserState>'.
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> 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.
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:
type
type Select<TBlocextendsStateContainerConstructor> =(state:ExtractState<TBloc>, bloc:InstanceReadonlyState<TBloc>)=>unknown[]
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> 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.
}[])=> number, initialValue: number): number (+2 overloads)
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. 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.
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.
Keep the selector referentially stable across renders (e.g. via
useCallback) — passing a fresh function each render forces the
subscription to re-key, which the underlying channel treats as a new
consumer.
select: (
state: Readonly<CartState>
state, bloc) => [bloc.
total: number
total,
state: Readonly<CartState>
state.
items: {
price: number;
qty: number;
}[]
items.
Array<{ price: number; qty: number; }>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.
length],
bloc: InstanceReadonlyState<typeof CartCubit>
});
return
const cart:InstanceReadonlyState<typeofCartCubit>
cart.
total: number
total;
}
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. Keep the selector referentially stable — a fresh function each render re-keys the subscription. 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 (verified in @blac/react’s types.ts):
type
type ArgsOption<TextendsStateContainerConstructor> =ExtractArgs<T> extendsvoid? {
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> 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<{
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. 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.
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> 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<
interface UserState
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. 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.
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.
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> 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<{
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
voidthis.
UserCubit.fetch(_id: string): Promise<void>
fetch(
args: {
userId: string;
}
args.
userId: string
userId);
}
UserCubit.retry(): void
retry() {
// the `args` GETTER is `Args | undefined` — guard it
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> 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({
items?: readonly string[] |undefined
items: [...this.
StructuralContainer<TodoState>.state: TodoState
state.
TodoState.items: string[]
items,
t: string
t] });
get
TodoCubit.count: number
count() {
returnthis.
StructuralContainer<TodoState>.state: TodoState
state.
TodoState.items: string[]
items.
Array<string>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.
length;
}
}
// Custom hook: no explicit return annotation needed — it's inferred as
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> 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<
interface UserState
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. 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.
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:
type TodoState =
type ExtractState<T> =TextendsStateContainerConstructor<inferSextendsobject> ?Readonly<S> :never
Extract the state type from a StateContainer
@template ― T - The StateContainer type
ExtractState<typeof
classTodoCubit
TodoCubit>;
type TodoState = {
readonly items:string[];
}
type
type TodoBloc =Omit<TodoCubit, "state"> & {
state:Readonly<{
items:string[];
}>;
}
TodoBloc=
type InstanceReadonlyState<TextendsStateContainerConstructor=any> =Omit<InstanceType<T>, "state"> & {
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.