A Cubit is a state container that holds a typed state value and exposes methods to change it. It is the class you subclass for almost everything in BlaC. Every signature on this page is quoted from the @blac/core source.
classCubit<
Sextendsobject=any,
Args=void,
Depsextendsobject=Record<string, never>,
> extendsStateContainer<S, Args, Deps> {}
Type parameter
Default
Description
S
any
The state shape. Must be an object type (S extends object); primitives like number or string are not supported as state.
Args
void
Serializable construction data delivered to init(args). See Args.
Deps
Record<string, never>
Non-serializable handles injected per consumer and read via this.deps. See Deps.
Cubit adds nothing structurally over StateContainer — it exists as a real class so instance instanceof Cubit works and so you have one obvious thing to extend.
BlaC has exactly two base types: StateContainer (the abstract engine) and Cubit (the concrete class you extend). There is no Bloc class — if you are coming from flutter_bloc, the closest equivalent is Cubit. We use the name “bloc” colloquially to mean “any state-container instance,” but the only thing you ever extend is Cubit. See the glossary for the full StateContainer / Cubit / bloc / instance hierarchy.
The state lives in a class for three concrete reasons:
State and the logic that changes it live together. Methods like addItem sit next to the state they mutate, so an action is a method call, not a reducer plus an action-type constant plus a dispatch.
Derived values are just getters. A get total() is computed on read and tracked automatically — no selector library, no memo wiring (see Getters).
Instances have identity and a lifecycle. The registry can create, key, ref-count, and dispose instances. That is what makes shared-vs-scoped state, args, and deps possible (see Instance Management).
For the deeper rationale (why proxy tracking over selectors, why microtask batching, how this compares to Redux/Zustand/MobX), see the Mental Model.
Low-level container options (custom per-path equality, skeleton hints). Most subclasses omit it.
Returns: a new instance. The registry always builds instances zero-arg, so your subclass constructor takes no arguments — it only calls super(initialState).
Behavior. Runs before init(args). Use it solely to set the initial state; args-derived setup belongs in init.
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.
State in BlaC is immutable from the outside: you never assign to this.state.x. Instead you hand the container a new state and it diffs the change, marks which paths moved, and wakes only the consumers that read those paths. The three methods below are three ways to produce that next state.
Replace the entire state. Use when you have the full new state ready.
emit(next: S): void
Parameter
Type
Required
Description
next
S
yes
The complete new state. Replaces the current state wholesale — it does not merge.
Returns:void.
Behavior.emit is a no-op when the next state is equal to the current one: it short-circuits if prev === next by reference, or if the configured equality function (default: shallow per-key Object.is) reports them equal. So emitting an object that happens to match the current state will not wake any consumers. Equality is configurable per class via blac({ equality }) and globally via configureBlac — see Configuration.
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.
Derive new state from the current state. Use when you need to read the current state to compute the next one.
update(fn: (state:S)=>S): void
Parameter
Type
Required
Description
fn
(state: S) => S
yes
A reducer receiving the current state and returning the complete next state.
Returns:void.
Behavior.update is sugar over emit: it calls this.emit(fn(currentState)), so it inherits emit’s equality short-circuit. Your fn must return the full next state — the same “no merge” rule as emit applies, so spread the existing state when you only change a few fields.
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.
Deep-merge partial changes into the current state. Use when you want to update some fields without touching others.
patch(partial: DeepPartial<S>): void
Parameter
Type
Required
Description
partial
DeepPartial<S>
yes
The subtree to merge. Nested objects can be patched without spreading the full structure; arrays, Date, Map, Set, and class instances are treated as atomic leaves.
Returns:void.
Behavior.patch deep-merges along plain-object branches and value-filters the result: a path is marked dirty only if its value actually changed. It skips the update entirely if all provided top-level values are already Object.is-equal to the current state (a shallow pre-spread no-op check), and deepMerge also returns the previous state by reference on a deep no-op.
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({
loading?: boolean |undefined
loading: true });
};
ProfileCubit.rename: (name:string)=>void
rename=(
name: string
name:string)=> {
// Only updates user.profile.name — age and other fields are preserved
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.
Getters are how you model derived state — values computed from other state rather than stored. Prefer a getter over storing a computed field: a stored total can drift out of sync with items, but a get total() is recomputed on every read and can never be stale.
A getter has no signature fence of its own — it is plain TypeScript on your subclass. In React render, it participates in auto-tracking:
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.
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.
A Cubit instance moves through a fixed sequence, and the input hooks slot into specific points:
Construct — new Type() runs your constructor, which calls super(initialState). The constructor takes no arguments; the registry always builds instances zero-arg.
init(args) — called once, synchronously, after construction and before any consumer reads the first snapshot. This is where args-derived setup belongs.
onDepsChanged(next, prev) — called whenever the merged per-consumer deps view changes (and once more on dispose, with everything cleared).
Mutations — emit / update / patch run as your methods are called.
Dispose — when the last consumer releases the instance (unless keepAlive); fires the dispose system event.
Steps 2 and 3 are the two input lanes: args for serializable identity data, deps for live non-serializable handles. The rest of this section covers each.
Seed args-derived state or kick off loads, once per instance, before the first snapshot.
protected init(args: Args): void
Parameter
Type
Required
Description
args
Args
yes
The args passed at acquire time (useBloc(Type, { args })). void when no Args generic is declared.
Returns:void.
Behavior. A protected lifecycle method — not callable from outside the class. The framework calls it once per instance, synchronously after new Type() and before the first state snapshot is read. It replaces the old setConfig/setProps patterns. When useBloc is called with { args }, those args are required at the call site (type error if omitted when Args != void), used to derive the instance identity (different args ⇒ different instance), and available synchronously in init before any consumer sees state. See Passing Inputs for the full args/identity model.
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.
Cubit<
interface UserCardState
UserCardState, {
userId: string
userId:string }> {
constructor() {
super({
UserCardState.name: string
name: '',
UserCardState.loading: boolean
loading: false });
}
// Constructor stays zero-arg. The framework calls init(args) before first snapshot.
protected
UserCardCubit.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.
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.
By default, instance identity is the structural hash of all args. Override with a static property on the class to control exactly which args distinguish one instance from another:
static key: (args:Args)=> string
Behavior. A function (args: Args) => string declared once on the class, not at every call site. When absent, BlaC hashes the full args object. Args not referenced by key ride along as config but do not fork instances.
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.
Declare a Deps type as the third generic parameter to receive non-serializable values (refs, callbacks, controller instances) that can’t go in args. Deps are read lazily via this.deps.x and may be undefined — always guard.
get deps(): Readonly<Deps>
Returns: a Readonly<Deps> view — the union of every consumer’s contributed slice.
Key properties of deps:
Never key identity — different refs don’t fork the instance.
Per-consumer merged — each useBloc call contributes its own slice; the bloc sees the union (last writer wins on key collisions).
Live — updated after each commit; may change over time. May legitimately be undefined, so always optional-chain.
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.
Cubit<
interface UploadState
UploadState,
{
endpoint: string
endpoint:string }, // Args
{
inputRef?: InputRef |undefined
inputRef?:
interface InputRef
InputRef } // Deps
> {
private
FileUploadCubit.endpoint: string
endpoint='';
constructor() {
super({
UploadState.progress: number
progress: 0 });
}
protected
FileUploadCubit.init(args: {
endpoint: 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.
For handles that require initialization when they arrive (a canvas element, a rich-text-editor controller), implement onDepsChanged. It fires after each deps merge with the new and previous combined views.
The merged view before it. Compare the two to detect arrivals and departures.
Returns:void.
Behavior. Optional — blocs that don’t declare it just read this.deps.x lazily. When declared, it gives the bloc clean acquire/release edges without any consumer-side cleanup wiring. It also fires once on dispose with every key cleared (so next.x is undefined), giving you a teardown edge.
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.
Cubit<
interface RenderState
RenderState,
{
sceneId: string
sceneId:string },
{
canvas?: HTMLCanvasElement |undefined
canvas?:
interface HTMLCanvasElement
The HTMLCanvasElement interface provides properties and methods for manipulating the layout and presentation of elements. The HTMLCanvasElement interface also inherits the properties and methods of the HTMLElement interface.
The HTMLCanvasElement interface provides properties and methods for manipulating the layout and presentation of elements. The HTMLCanvasElement interface also inherits the properties and methods of the HTMLElement interface.
Args identifying which keyed instance to resolve when an accessor is called without its own args.
Returns: a DepHandle<T> with two accessors — handle.untracked() resolves the dep against the registry lazily on each call (this.user.untracked()), and handle.track() does the same plus subscribes the reading React consumer. Both take an optional { args } to override defaultArgs per call. See Auto-tracking with .track().
Behavior. Records the dependency, then returns the handle. Resolving on every call keeps the surface immune to dep-instance churn. Note: .untracked() does not auto-resubscribe to the dep’s channel — consumers that need reactive updates use .track() (or subscribe explicitly via useBloc’s tracker). A naive always-on auto-bridge would cycle on mutual deps, which is why tracking is opt-in.
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.
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.
Declare a cross-bloc dependency. Returns a branded handle with two
accessors — the dep instance is resolved against the registry on each call,
which keeps the surface immune to dep-instance churn:
handle.track(options?) — reactive read. Returns [state, instance].
Inside a getter reached through the React proxy this subscribes the
reading component to the dep's changes (base impl: live, no subscription).
handle.untracked(options?) — returns the live instance with no tracking,
for imperative method calls and one-off reads.
defaultArgs resolves the dep instance when an accessor is called without
its own args; per-call options.args overrides it and can derive from
current state. This does NOT auto-resubscribe outside the React proxy;
non-React consumers needing updates should subscribe explicitly.
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.
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.
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(
function(localvar)e: unknown
e) });
}
};
}
The requestId pattern discards responses from superseded requests. Each new load() call increments the ID, and callbacks from previous calls see a mismatch and bail out.
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.
Cubit<{
items: string[]
items:string[];
filter: "all"|"done"
filter:'all'|'done';
}> {
constructor() {
super({
items: string[]
items: [],
filter: "all"|"done"
filter: 'all' });
}
// state: { items: [...], filter: 'all' }
TodoCubit.bad: ()=>void
bad=()=> {
// @ts-expect-error — items is required; emit replaces the whole state
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.
Symptom: DevTools shows a new bloc instance being created and destroyed on every render, or the circuit-breaker throws “max instances exceeded.”
Cause: A non-serializable value (function, ref, class instance) is in args. Because args must be JSON-serializable, a fresh object reference each render produces a different hash and therefore a different instance key.
Fix: Move non-serializable values to the bloc’s Deps lane — they never affect instance identity:
// Non-serializable in args → new instance every render (throws in dev)