Typesafe state machines with minimal boilerplate. For React, Svelte and more.
State machines are powerful but often verbose. muxmuxmux gives you the benefits of state-dependent behavior with minimal boilerplate:
get()/set()/subscribe() interfaceimport { controller } from "@muxmuxmux/core";
const handleClick = async () => {
button.set("loading");
try {
await submitForm();
button.set("idle");
} catch (error) {
button.set("error");
}
};
const button = controller("idle", {
idle: { onClick: handleClick },
loading: {}, // Button disabled during loading
error: { onClick: handleClick }, // Retry on error
});
// Read state
button.get(); // 'idle'
button.subscribe((state) => updateButtonUI(state));
// User clicks button
button.onClick(); // idle → loading → idle/error
import { useController } from "@muxmuxmux/react";
import { useCallback } from "react";
function Button() {
// Expected stable identity to be used with useController
const handleClick = useCallback(async () => {
button.set("loading");
try {
await submitForm();
button.set("idle");
} catch (error) {
button.set("error");
}
}, []);
const button = useController("idle", {
idle: { onClick: handleClick },
loading: {},
error: { onClick: handleClick }, // Retry on error
});
const state = button.get();
return (
<button
onClick={button.onClick}
disabled={state === "loading"}
>
{state === "loading" ? "Loading..." : "Submit"}
</button>
);
}
Svelte works with @muxmuxmux/core directly. The controller implements Svelte's store interface.
<script lang="ts">
import { controller } from "@muxmuxmux/core";
const handleSubmit = async () => {
button.set("loading");
try {
await submitForm();
button.set("idle");
} catch (error) {
button.set("error");
}
};
const button = controller("idle", {
idle: { onClick: handleSubmit },
loading: {},
error: { onClick: handleSubmit }, // Retry
});
</script>
<button on:click={button.onClick}>
{$button === "loading" ? "Loading..." : "Submit"}
</button>
controller(defaultState, methods) creates an observable state controller.
The controller is both:
get(), set(), subscribe() for state managementconst ctrl = controller("idle", {
idle: { start: () => {} },
running: { stop: () => {} },
});
// Observable store interface
ctrl.get(); // Current state
ctrl.set("running"); // Change state
ctrl.subscribe(fn); // Listen to changes
// Method dispatcher
ctrl.start(); // Calls idle.start() when state is 'idle'
ctrl.stop(); // Calls running.stop() when state is 'running'
// No-op if method doesn't exist in current state
Note: Svelte works with @muxmuxmux/core directly via its native store interface.
See CONTRIBUTING.md for development setup and guidelines.
Issues and feature requests are welcome at github.com/vadirn/muxmuxmux/issues.
MIT