import { } from "@effectionx/state-api"State API
Reactive state container with typed reducers and middleware for Effection.
Usage
Basic state
import { run, each, spawn } from "effection";
import { useState } from "@effectionx/state-api";
await run(function* () {
const counter = yield* useState(0);
yield* counter.set(42);
yield* counter.update((n) => n + 1);
const value = yield* counter.get(); // 43
});
Typed reducers
Define named state transitions that are type-safe and interceptable.
import { run } from "effection";
import { useState } from "@effectionx/state-api";
interface Todo {
id: number;
text: string;
done: boolean;
}
await run(function* () {
const todos = yield* useState([] as Todo[], {
add: (state, text: string) => [
...state,
{ id: state.length, text, done: false },
],
toggle: (state, id: number) =>
state.map((t) => (t.id === id ? { ...t, done: !t.done } : t)),
remove: (state, id: number) => state.filter((t) => t.id !== id),
});
yield* todos.add("buy milk"); // returns the new state
yield* todos.toggle(0);
yield* todos.remove(0);
// built-in operations still available
yield* todos.set([]);
yield* todos.update((s) => [...s]);
const snapshot = yield* todos.get();
});
Each reducer is a function (state, ...args) => newState. The state
argument is injected automatically; callers pass only the remaining
arguments.
Stream subscription
State<T> implements Stream<T, void>, so you can subscribe to changes.
The following continues from the todos example above:
// inside run(function* () { ... })
yield* spawn(function* () {
for (const snapshot of yield* each(todos)) {
console.log("todos changed:", snapshot);
yield* each.next();
}
});
Middleware
Every operation (set, update, get, and all reducer actions) can be
intercepted with middleware via around():
// inside run(function* () { ... })
// log every state change
yield* todos.around({
*set([value], next) {
console.log("replacing state:", value);
return yield* next(value);
},
*add([text], next) {
console.log("adding todo:", text);
return yield* next(text);
},
});
// validate state transitions
yield* counter.around({
*set([value], next) {
if (value < 0) throw new Error("counter cannot be negative");
return yield* next(value);
},
});
// modify arguments
yield* counter.around({
*update([fn], next) {
return yield* next((n) => Math.max(0, fn(n)));
},
});
Middleware is scoped: it applies only within the current Effection scope and is automatically removed when the scope exits.
API Reference
Type Parameters
T
R extends ReducerMap<T>
Parameters
initial: T
reducers: R
Return Type
Operation<State<T, R>>
function useState<T, R extends ReducerMap<T>>(initial: T, reducers?: R): Operation<State<T, EmptyReducers> | State<T, R>>
A reactive state container with middleware support.
Provides built-in set, update, and get operations,
plus any user-defined reducer actions. All operations return
Operation<T> where T is the state type, yielding the
resulting state after the operation.
Also implements Stream<T, void> so you can subscribe to
state changes using each.
Type Parameters
T
R extends ReducerMap<T> = Record<never, never>