@synergyeffect/with-state-match
v1.1.3
Published
A higher-order component to decleratively program small stateful components.
Downloads
50
Readme
with-state-match
🌀 Minimal, declarative state matcher for React. Build async/stateful UI flows with no boilerplate state machines.
✨ Features
- 🪶 Tiny HOC — no dependencies
- 🔑 Explicit state names (you control the vocabulary)
- 🧩 Simple API:
withStateMatch(defaultState, work, matcher) - ⚡ TypeScript-first — state names, props, and context all autocomplete
- 🌀 Curried
chstate:chstate("pending")()orchstate("success")(data) - 🎯 Perfect for buttons, async flows, or lightweight UI state machines
🚀 Install
npm install @synergyeffect/with-state-matchor with Yarn:
yarn add @synergyeffect/with-state-match🧑💻 Usage
import { withStateMatch } from "@synergyeffect/with-state-match";
import { Button } from "./ui/Button";
import { api } from "./api";
// Example async context type
type Invoice = { id: string };
export const ButtonDownloadInvoice = withStateMatch(
"idle", // 👈 initial state
({ props, chstate }) => {
chstate("pending")();
api.insurance
.client({ insurance: props.insurance, price: props.price })
.fork(chstate("error"), chstate("success"));
},
{
idle: ({ run }) => <Button onClick={run}>Download Invoice</Button>,
pending: () => (
<Button disabled className="animate-pulse">
Downloading…
</Button>
),
success: ({ stateData }) => (
<Button className="text-green-500">✅ Invoice #{stateData?.id}</Button>
),
error: ({ stateData, run }) => (
<Button onClick={run} className="text-red-500">
❌ Failed: {String(stateData)}
</Button>
),
},
);📖 API
withStateMatch(defaultState, work, matcher)
defaultState: stringInitial state name (must be one ofmatcher’s keys).work: (tools, ...args) => voidEffectful function that runs whenrun()is called. Receives:props(original component props)chstate(state)(data)(curried state transition)
matcher: Record<string, (tools) => ReactNode>Declarative mapping ofstate → renderer. Each renderer gets:stateData: current context payloadprops: original propschstate: curried transition functionrun: executes theworkfunction
🎯 Why?
Managing async UI in React often leads to:
- Repetitive
useStateboilerplate - Over-engineered state machines
- Mixing rendering with imperative effects
with-state-match gives you:
- Explicit, declarative states
- Separation of effects (
work) from rendering (matcher) - Tiny surface area → easy to read & maintain
📦 License
MIT © SynergyEffect
