TypeScript Daq state machine mach
component isLoading, isError, hasData, isEmpty separate booleans rap ghItlh vaj bug ship: cha' booleans true wa' poH, UI spinner 'ej error message stacked. State machine type level Daq fix: independent flags (nonsense combine) pagh, wa' named state poH wa', transitions clearly defined.
boolean-soup Qagh
component state state machines replace:
interface FetchState {
isLoading: boolean;
isError: boolean;
data: string[] | null;
error: string | null;
}
loS fields, wa'maH jav combinations, loS neH valid (loading, success, error, idle). isLoading: true 'ej isError: true wa' poH Dach pagh - qaw poQ, Hoch Daq, reH.
states union model
state machine "DaH nuq qaS" wa' tagged union (flags bag pagh):
type FetchState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: string[] }
| { status: "error"; message: string };
DaH data status "success" vaj neH tu'; message status "error" vaj neH. TypeScript control-flow narrowing enforce:
function render(state: FetchState): string {
switch (state.status) {
case "idle":
return "Nothing yet.";
case "loading":
return "Loading…";
case "success":
return `Got ${state.data.length} items.`; // data is known to exist here
case "error":
return `Failed: ${state.message}`; // message is known to exist here
}
}
"error" branch Daq state.data access vaj TypeScript compile time reject - 2am user blank screen report pagh.
transitions chel
state wa' snapshot. state machine second half allowed transitions: nuq states pIm states Daq jaH, nuq event. events states rap model:
type FetchEvent =
| { type: "FETCH" }
| { type: "RESOLVE"; data: string[] }
| { type: "REJECT"; message: string }
| { type: "RESET" };
function transition(state: FetchState, event: FetchEvent): FetchState {
switch (state.status) {
case "idle":
if (event.type === "FETCH") return { status: "loading" };
return state;
case "loading":
if (event.type === "RESOLVE") return { status: "success", data: event.data };
if (event.type === "REJECT") return { status: "error", message: event.message };
return state;
case "success":
case "error":
if (event.type === "RESET") return { status: "idle" };
return state;
}
}
function branch Hoch explicit allowed. "idle" "success" direct accident jump laHbe' - code path pagh. future requirement transition poQ vaj deliberately wa' place Daq chel (component three booleans wrong order set side effect pagh).
component Daq wire
transition function pure - side effects pagh, unit test Dun. actual async work wrap thin layer:
class FetchController {
private state: FetchState = { status: "idle" };
private listeners: Array<(s: FetchState) => void> = [];
private setState(next: FetchState) {
this.state = next;
this.listeners.forEach((listener) => listener(next));
}
subscribe(listener: (s: FetchState) => void) {
this.listeners.push(listener);
listener(this.state);
}
async load(url: string) {
this.setState(transition(this.state, { type: "FETCH" }));
try {
const res = await fetch(url);
const data: string[] = await res.json();
this.setState(transition(this.state, { type: "RESOLVE", data }));
} catch (err) {
const message = err instanceof Error ? err.message : "Unknown error";
this.setState(transition(this.state, { type: "REJECT", message }));
}
}
}
state change Hoch transition Daq - "valid combinations neH reachable" guarantee controller whole lifetime Daq (type level one render neH pagh).
transitions test (components pagh)
transition plain function, dependencies pagh: path Hoch test, mount pagh:
test("FETCH moves idle to loading", () => {
const result = transition({ status: "idle" }, { type: "FETCH" });
expect(result).toEqual({ status: "loading" });
});
test("REJECT during loading carries the error message", () => {
const result = transition(
{ status: "loading" },
{ type: "REJECT", message: "network down" },
);
expect(result).toEqual({ status: "error", message: "network down" });
});
test("FETCH while already loading is a no-op", () => {
const state: FetchState = { status: "loading" };
const result = transition(state, { type: "FETCH" });
expect(result).toBe(state);
});
last test qatlh: duplicate FETCH event (loading already) restart pagh, request double-fire pagh. document 'ej enforce.
overkill
two-state toggle (open / closed) machinery poQ pagh - wa' boolean Dun. explicit state machine: component wej+ mutually exclusive states, transitions bugs source (loading states classic), if (isLoading && !isError && data) guards ghItlh.
yaj
Booleans multiply - loS flags = wa'maH jav states, law' nonsense. tagged union valid states neH. wa' transition function "nuq next" decide. compiler rest enforce.