Skip to content

Reset to Initial State

Use when: a form, wizard, or filter panel needs a “Clear” or “Cancel” button that restores every field to its defaults in one action. Don’t use when: the reset is partial (only some fields) — prefer patch with the specific defaults rather than a full state replace.

Store the initial state once in the constructor and call emit with it to restore it in one atomic update.

const
const DEFAULT_FILTER: FilterState
DEFAULT_FILTER
:
interface FilterState
FilterState
= {
FilterState.query: string
query
: '',
FilterState.category: string
category
: 'all',
FilterState.minPrice: number
minPrice
: 0,
FilterState.maxPrice: number
maxPrice
: 10_000,
FilterState.sortBy: "price" | "rating" | "newest"
sortBy
: 'newest',
};
class
class FilterCubit
FilterCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, 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 (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.

Cubit
<
interface FilterState
FilterState
> {
// Keep the initial state immutable — emit makes a reference check so we
// must not mutate this object in place.
private readonly
FilterCubit.initial: FilterState
initial
:
interface FilterState
FilterState
;
constructor(
initial: FilterState
initial
:
interface FilterState
FilterState
=
const DEFAULT_FILTER: FilterState
DEFAULT_FILTER
) {
super(
initial: FilterState
initial
);
this.
FilterCubit.initial: FilterState
initial
=
initial: FilterState
initial
;
}
FilterCubit.setQuery: (query: string) => void
setQuery
= (
query: string
query
: string) => this.
StateContainer<FilterState, void, Record<string, never>>.patch(partial: {
query?: string | undefined;
category?: string | undefined;
minPrice?: number | undefined;
maxPrice?: number | undefined;
sortBy?: "price" | "rating" | "newest" | undefined;
}): void

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.

patch
({
query?: string | undefined
query
});
FilterCubit.setCategory: (category: string) => void
setCategory
= (
category: string
category
: string) => this.
StateContainer<FilterState, void, Record<string, never>>.patch(partial: {
query?: string | undefined;
category?: string | undefined;
minPrice?: number | undefined;
maxPrice?: number | undefined;
sortBy?: "price" | "rating" | "newest" | undefined;
}): void

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.

patch
({
category?: string | undefined
category
});
FilterCubit.setMinPrice: (minPrice: number) => void
setMinPrice
= (
minPrice: number
minPrice
: number) => this.
StateContainer<FilterState, void, Record<string, never>>.patch(partial: {
query?: string | undefined;
category?: string | undefined;
minPrice?: number | undefined;
maxPrice?: number | undefined;
sortBy?: "price" | "rating" | "newest" | undefined;
}): void

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.

patch
({
minPrice?: number | undefined
minPrice
});
FilterCubit.setMaxPrice: (maxPrice: number) => void
setMaxPrice
= (
maxPrice: number
maxPrice
: number) => this.
StateContainer<FilterState, void, Record<string, never>>.patch(partial: {
query?: string | undefined;
category?: string | undefined;
minPrice?: number | undefined;
maxPrice?: number | undefined;
sortBy?: "price" | "rating" | "newest" | undefined;
}): void

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.

patch
({
maxPrice?: number | undefined
maxPrice
});
FilterCubit.setSortBy: (sortBy: FilterState["sortBy"]) => void
setSortBy
= (
sortBy: "price" | "rating" | "newest"
sortBy
:
interface FilterState
FilterState
['sortBy']) => this.
StateContainer<FilterState, void, Record<string, never>>.patch(partial: {
query?: string | undefined;
category?: string | undefined;
minPrice?: number | undefined;
maxPrice?: number | undefined;
sortBy?: "price" | "rating" | "newest" | undefined;
}): void

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.

patch
({
sortBy?: "price" | "rating" | "newest" | undefined
sortBy
});
/** Restore all fields to their initial values in one emit. */
FilterCubit.reset: () => void

Restore all fields to their initial values in one emit.

reset
= () => {
// emit replaces the whole state; patch would also work but emit is
// clearer in intent — we're not updating a subset, we're replacing all.
this.
StateContainer<FilterState, void, Record<string, never>>.emit(next: FilterState): void
emit
({ ...this.
FilterCubit.initial: FilterState
initial
});
// Spread produces a new object reference so the equality check can
// detect a change even if the current state already matches the defaults.
};
get
FilterCubit.isDirty: boolean
isDirty
() {
const
const s: FilterState
s
= this.
StructuralContainer<FilterState>.state: FilterState
state
;
return (
const s: FilterState
s
.
FilterState.query: string
query
!== this.
FilterCubit.initial: FilterState
initial
.
FilterState.query: string
query
||
const s: FilterState
s
.
FilterState.category: string
category
!== this.
FilterCubit.initial: FilterState
initial
.
FilterState.category: string
category
||
const s: FilterState
s
.
FilterState.minPrice: number
minPrice
!== this.
FilterCubit.initial: FilterState
initial
.
FilterState.minPrice: number
minPrice
||
const s: FilterState
s
.
FilterState.maxPrice: number
maxPrice
!== this.
FilterCubit.initial: FilterState
initial
.
FilterState.maxPrice: number
maxPrice
||
const s: FilterState
s
.
FilterState.sortBy: "price" | "rating" | "newest"
sortBy
!== this.
FilterCubit.initial: FilterState
initial
.
FilterState.sortBy: "price" | "rating" | "newest"
sortBy
);
}
}
function FilterPanel() {
const [state, filter] = useBloc(FilterCubit, {
select: (s, bloc) => [s.query, s.category, s.sortBy, bloc.isDirty],
});
return (
<div>
<input
value={state.query}
onChange={(e) => filter.setQuery(e.target.value)}
placeholder="Search…"
/>
<select
value={state.category}
onChange={(e) => filter.setCategory(e.target.value)}
>
<option value="all">All</option>
<option value="books">Books</option>
<option value="music">Music</option>
</select>
{filter.isDirty && <button onClick={filter.reset}>Clear filters</button>}
</div>
);
}

When the initial state comes from args (for example a per-user default saved on the server), capture it in init instead of the constructor:

class
class UserFilterCubit
UserFilterCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, 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 (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.

Cubit
<
interface FilterState
FilterState
,
interface FilterState
FilterState
> {
private
UserFilterCubit.savedInitial: FilterState
savedInitial
!:
interface FilterState
FilterState
;
constructor() {
super({
FilterState.query: string
query
: '',
FilterState.category: string
category
: 'all' });
}
protected override
UserFilterCubit.init(args: FilterState): 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: FilterState
args
:
interface FilterState
FilterState
) {
// args is the authoritative initial state for this user.
this.
UserFilterCubit.savedInitial: FilterState
savedInitial
=
args: FilterState
args
;
this.
StateContainer<FilterState, FilterState, Record<string, never>>.emit(next: FilterState): void
emit
({ ...
args: FilterState
args
});
}
UserFilterCubit.reset: () => void
reset
= () => {
this.
StateContainer<FilterState, FilterState, Record<string, never>>.emit(next: FilterState): void
emit
({ ...this.
UserFilterCubit.savedInitial: FilterState
savedInitial
});
};
}
  • Cubitemit replaces state; patch deep-merges
  • Patterns — named instances for parallel forms