Async Commands
These are the v1 docs. They are not maintained.
This page is a verbatim snapshot of the commiq 1.x documentation, kept for people still running 1.x. It is not corrected and not tested against any release. Several pages contain claims that were wrong when written: examples that do not run as printed, sealStore described as read-only when it was not, handledEvent presented as a working subscription pattern when it never matched, and effects described as swallowing only AbortError when they swallowed everything.
Do not treat anything here as a statement about how commiq 2.x behaves.
What changed in 2.0 and how to upgrade · Current documentation
Site search only indexes the current docs. Use the sidebar to browse these pages.
Async Commands
Command handlers can be async. This is useful for API calls, timers, or any asynchronous operation.
Key behavior: The store processes commands sequentially. When a handler is async, the queue waits for it to complete before picking up the next command. This guarantees ordering and prevents race conditions.
Example: Fetching Users
Store
import {
createStore,
createCommand,
createEvent,
sealStore,
} from "@naikidev/commiq";
type AsyncState = {
users: User[];
loading: boolean;
error: string;
};
const fetchCompleted = createEvent<{ count: number }>("fetchCompleted");
const fetchFailed = createEvent<{ error: string }>("fetchFailed");
const _store = createStore<AsyncState>({
users: [],
loading: false,
error: "",
});
_store.addCommandHandler("fetchUsers", async (ctx) => {
// 1. Enter loading state
ctx.setState({ ...ctx.state, loading: true, error: "" });
try {
// 2. Async work
const response = await fetch("/api/users");
const users = await response.json();
// 3. Update state with result
ctx.setState({
users: [...ctx.state.users, ...users],
loading: false,
error: "",
});
// 4. Emit success event
ctx.emit(fetchCompleted, { count: users.length });
} catch (err) {
// 5. Handle failure
ctx.setState({
...ctx.state,
loading: false,
error: err.message,
});
ctx.emit(fetchFailed, { error: err.message });
}
});
export const asyncStore = sealStore(_store);
export const fetchUsers = () => createCommand("fetchUsers", undefined);React Component
function UserList() {
const { users, loading, error } = useSelector(asyncStore, (s) => s);
const queue = useQueue(asyncStore);
const [log, setLog] = useState<string[]>([]);
// Listen to events for side-effects
useEvent(asyncStore, fetchCompleted, (e) => {
setLog((prev) => [...prev, `Fetched ${e.data.count} users`]);
});
useEvent(asyncStore, fetchFailed, (e) => {
setLog((prev) => [...prev, `Error: ${e.data.error}`]);
});
return (
<div>
<button onClick={() => queue(fetchUsers())} disabled={loading}>
{loading ? "Loading…" : "Fetch Users"}
</button>
{error && <p className="error">{error}</p>}
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
</div>
);
}Important Notes
- Sequential processing — while an async handler runs, the queue waits. The next command runs only after the handler's promise resolves.
- State is live —
ctx.statealways reflects the latest state, even if you calledsetStateearlier in the same handler. - Error handling — use try/catch inside the handler. If an uncaught error is thrown, the store emits a
commandHandlingErrorbuiltin event automatically. - Loading patterns — set
loading: trueat the start of the handler andloading: falseat the end. React components subscribed viauseSelectorwill re-render at eachsetStatecall.