Counter State Machine
count0
import { createMachine, defineStates, setup, effect, createStoreMachine, addStoreApi, withSubscribe,} from "matchina";
export const createCounterMachine = () => { const states = defineStates({ Active: undefined, Inactive: undefined, });
const store = createStoreMachine( { count: 0 }, { increment: () => (change) => ({ count: change.from.count + 1 }), decrement: () => (change) => ({ count: change.from.count - 1 }), reset: () => () => ({ count: 0 }), } ); const storeWithApi = addStoreApi(withSubscribe(store));
const machine = createMachine( states, { Active: { increment: "Active", decrement: "Active", reset: "Active", deactivate: "Inactive", }, Inactive: { activate: "Active", }, }, "Active" );
setup(machine)( effect((ev) => { if (ev.to.is("Active") && ev.type in storeWithApi.api) { (storeWithApi.api as any)[ev.type](); } }) );
// Encapsulate store and expose methods on machine const enhancedMachine = Object.assign(machine, { store: storeWithApi, increment: () => storeWithApi.api.increment(), decrement: () => storeWithApi.api.decrement(), reset: () => storeWithApi.api.reset(), getCount: () => storeWithApi.getState().count, });
return enhancedMachine;};
export type CounterMachine = ReturnType<typeof createCounterMachine>;import { useMachine } from "matchina/react";import { type CounterMachine } from "./machine";
export const CounterView = ({ machine }: { machine: CounterMachine }) => { useMachine(machine); useMachine(machine.store);
const count = machine.getCount(); const isActive = machine.getState().is("Active");
return ( <div className="flex flex-col items-center gap-6"> <div className="flex flex-col items-center gap-1"> <span className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground">count</span> <span className="text-6xl font-semibold text-foreground tabular-nums">{count}</span> </div>
<div className="flex gap-2"> <button className="btn btn-outline" onClick={() => machine.decrement()} disabled={!isActive}>−</button> <button className="btn btn-primary" onClick={() => machine.increment()} disabled={!isActive}>+</button> </div>
<div className="flex gap-2"> <button className="btn btn-ghost btn-sm" onClick={() => machine.reset()} disabled={!isActive}>Reset</button> <button className={`btn btn-sm ${isActive ? "btn-outline" : "btn-primary"}`} onClick={() => isActive ? machine.send("deactivate") : machine.send("activate")} > {isActive ? "Deactivate" : "Activate"} </button> </div> </div> );};import { useMemo } from "react";import { CounterView } from "./CounterView";import { createCounterMachine } from "./machine";
export function CounterDemo() { const machine = useMemo(createCounterMachine, []); return <CounterView machine={machine} />;}