# ccstate > This document records common patterns and best practices when using ccstate in the vm0 platform. - Author: github-actions[bot] - Repository: fagemx/vm0 - Version: 20260206190120 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-06 - Source: https://github.com/fagemx/vm0 - Web: https://mule.run/skillshub/@@fagemx/vm0~ccstate:20260206190120 --- # ccstate Patterns and Best Practices This document records common patterns and best practices when using ccstate in the vm0 platform. ## DOM Callback Pattern When handling DOM events (like button clicks) that trigger async commands, follow this pattern: ### Problem DOM event handlers that call async commands will trigger TypeScript lint error `@typescript-eslint/no-floating-promises`. ### Solution Use the `detach()` function with `Reason.DomCallback` to explicitly mark the promise as intentionally fire-and-forget. ### Pattern ```typescript import { useSet, useGet } from "ccstate-react"; import { pageSignal$ } from "../../signals/page-signal.ts"; import { detach, Reason } from "../../signals/utils.ts"; import { someCommand$ } from "../../signals/some-command.ts"; function MyComponent() { const commandFn = useSet(someCommand$); const pageSignal = useGet(pageSignal$); const handleClick = () => { detach(commandFn(pageSignal), Reason.DomCallback); }; return ; } ``` ### Key Points 1. **Always use `pageSignal$`**: Get the page signal using `useGet(pageSignal$)` instead of creating a new `AbortController` 2. **Use `detach()` instead of `void`**: The `detach()` function properly handles promise rejection and tracks the promise for testing 3. **Use `Reason.DomCallback`**: This enum value indicates the promise is from a DOM event handler 4. **Never use `void` operator**: Using `void` silences the lint error but doesn't properly handle the promise ### Why This Pattern - **Proper abort handling**: `pageSignal$` is automatically aborted when the page unmounts, canceling any in-flight operations - **Test tracking**: `detach()` collects promises in test environments for proper cleanup - **Error handling**: `detach()` re-throws non-abort errors to ensure bugs aren't silenced - **Semantic clarity**: `Reason.DomCallback` documents why the promise is intentionally detached ### Other Reason Types ```typescript export enum Reason { DomCallback = "dom_callback", // For DOM event handlers Entrance = "entrance", // For application entry points Deferred = "deferred", // For deferred operations Daemon = "daemon", // For background daemon processes } ``` ## Related Patterns ### Getting pageSignal$ in Components ```typescript import { useGet } from "ccstate-react"; import { pageSignal$ } from "../../signals/page-signal.ts"; function MyComponent() { const pageSignal = useGet(pageSignal$); // Use pageSignal to call commands } ``` ### pageSignal$ is Automatically Set by Route System **Important**: You do NOT need to manually set `pageSignal$` in your setup commands. The route system automatically handles this through `setupPageWrapper`. ```typescript // ✅ Correct: setupPageWrapper automatically sets pageSignal$ export const setupLogsPage$ = command(({ set }, signal: AbortSignal) => { // NO need to call set(setPageSignal$, signal) - it's automatic! // Just do your page-specific initialization set(initLogs$, signal); set(updatePage$, createElement(LogsPage)); }); // In bootstrap.ts, routes use setupAuthPageWrapper which calls setupPageWrapper: const ROUTE_CONFIG = [ { path: "/logs", setup: setupAuthPageWrapper(setupLogsPage$), // Wrapper sets pageSignal$ automatically }, ]; ``` **How it works**: 1. Route navigation triggers `loadRoute$` (in route.ts) 2. `loadRoute$` calls `setupAuthPageWrapper(setupLogsPage$)` 3. `setupAuthPageWrapper` internally calls `setupPageWrapper` 4. `setupPageWrapper` sets `pageSignal$` before calling your setup command 5. Your setup command receives the signal and can access `pageSignal$` in components **Never manually set pageSignal$ in setup commands** - the wrapper does it for you.