Redux and BlaC share the same design principle: a single source of truth per concern, immutable state
updates, and first-party DevTools support. The difference is in the mechanism. Redux routes every mutation
through a dispatcher and a reducer; BlaC routes it through a method on a class. The result is less
indirection, less boilerplate, and auto-tracked re-renders — but the tradeoff is giving up Redux’s strict,
serializable action log.
If you use Redux Toolkit today, this page maps each RTK primitive to its BlaC equivalent and walks
through a full side-by-side port.
RTK’s slice defines reducers keyed by action type; dispatch routes incoming action objects to the
matching reducer. BlaC removes the intermediary. The action name is the method name; the reducer is
the method body; and calling the method is the dispatch. One step instead of three.
// RTK — three artifacts per mutation
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
}, // reducer
incrementByAmount: (state, action) => {
state.value += action.payload; // reducer + payload type
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 (see A2 audit).
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). If a caller
needs the old "skip if no real change" patch semantics, they can wrap
patch themselves or call emit after a manual equality check.
RTK ships Immer so reducers can write state.value += 1 (mutable syntax compiled to immutable
updates). BlaC state is always immutable: emit(next) and update(fn) replace the whole state
object, and patch(partial) deep-merges a partial. You never mutate this.state in place:
// RTK would let you write: state.items.push(item)
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 (see A2 audit).
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). If a caller
needs the old "skip if no real change" patch semantics, they 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 legacy
listeners and 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 legacy
listeners and 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.
Returns the elements of an array that meet the condition specified in a callback function.
@param ― predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
@param ― thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
filter((
i: Item
i)=>
i: Item
i.
Item.id: string
id!==
id: string
id) });
}
patch deep-merges, so you only mention the key you are changing. emit and update replace the
whole state — list every key, or spread the previous 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 (see A2 audit).
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). If a caller
needs the old "skip if no real change" patch semantics, they 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 legacy
listeners and 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.
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@param ― callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.
@param ― thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
Override of StructuralContainer.patch that routes through the
StateContainer concerns: disposed guard, dev-only emit-rate check,
_changedWhileHydrating flag, pending-change capture (so legacy
listeners and 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.
Returns the elements of an array that meet the condition specified in a callback function.
@param ― predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
@param ― thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
RTK’s thunk factory adds a lifecycle (pending / fulfilled / rejected) dispatched as separate
action objects. BlaC async is a plain async method that calls emit as it goes:
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 (see A2 audit).
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). If a caller
needs the old "skip if no real change" patch semantics, they can wrap
patch themselves or call emit after a manual equality check.
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(
function(localvar)e: unknown
e) });
}
};
}
The requestId guard replaces RTK’s thunk cancellation — a newer call wins and the older one drops
its result. No AbortController needed for this pattern, though BlaC supports it too.
BlaC derives values in a getter on the class. Auto-tracking records the getter’s underlying reads, so
a component can read cart.total and stay subscribed to the source paths without a separate selector:
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 (see A2 audit).
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). If a caller
needs the old "skip if no real change" patch semantics, they 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.
reduce((
sum: number
sum,
i: CartItem
i)=>
sum: number
sum+
i: CartItem
i.
CartItem.price: number
price*
i: CartItem
i.
CartItem.qty: number
qty, 0);
}
}
A component that reads cart.total during render re-renders when the getter’s source paths change — no memoization layer, no Reselect import. Use select if the computed total itself should gate re-renders.
The plugin shows every Cubit’s state changes, diffs, and method calls in the same Redux DevTools panel.
State is diffed at the field level; you can step forward and backward through mutations.
BlaC has no equivalent. The registry is global, implicit, and automatic. Components call useBloc and
the registry creates instances on first use, shares them, and disposes them when the last consumer
unmounts. No bootstrap, no provider tree.
Redux’s strict serializable action log is a genuine architectural choice, not just boilerplate. Reach
for it when:
Your team needs a comprehensive audit trail of every state transition (e.g. regulated industries,
complex undo/redo flows over many slices).
You have large-team conventions and tooling already built around RTK (code generators, lint rules,
saga/observable middleware).
RTK Query is doing meaningful work for you (caching, deduplication, polling).
For most product apps where “I need shared, testable state logic” is the driver, BlaC removes the
ceremony without removing the testability or the DevTools story.