/// /** * TEST PATTERN: Top-Level Button with Disabled Attribute - WORKING * * CLAIM: Always render buttons at top level, use disabled attribute for conditional state * SOURCE: folk_wisdom/onclick-handlers-conditional-rendering.md * * WHAT THIS TESTS: * Demonstrates the RECOMMENDED approach: render buttons at top level (not inside * conditional blocks), use disabled attribute for conditional states, and handle * conditions inside the handler with early returns. * * EXPECTED BEHAVIOR: * - Pattern deploys successfully * - Button always rendered, never hidden * - Button disabled when pending=true * - Button enabled when pending=false * - Clicking enabled button increments count successfully * - No ReadOnlyAddressError * - No infinite loops * - derive() used ONLY for button content and disabled state (not wrapping button) * * MANUAL VERIFICATION STEPS: * 1. Deploy this pattern to a test space * 2. Verify button is always visible * 3. Check "Simulate pending" checkbox - button should become disabled * 4. Uncheck "Simulate pending" - button should become enabled * 5. Click "Increment" button - count should increase * 6. Verify no errors in browser console * 7. Verify smooth, reactive behavior * * FRAMEWORK MECHANISM: * - Button exists at top-level JSX (not inside derive/ifElse) * - derive() used only for attribute values (disabled) and content * - Handler receives real cell references, not read-only proxies * - .set() operations succeed because cells are writable */ import { Cell, computed, Default, handler, NAME, pattern, UI, } from "commontools"; interface State { count: Default; pending: Default; isProcessing: Default; } const incrementHandler = handler< unknown, { count: Cell; pending: Cell; isProcessing: Cell } >((_event, { count, pending, isProcessing }) => { // Handler handles conditions internally with early return if (pending.get() || isProcessing.get()) { console.log("Handler skipped: disabled state"); return; // Early return if not ready } // Do the actual work count.set(count.get() + 1); }); export default pattern(({ count, pending, isProcessing }) => { // computed() unwraps captured values - no .get() needed const isDisabled = computed(() => pending || isProcessing); const buttonText = computed(() => pending ? "Processing..." : "Increment (WORKING)" ); return { [NAME]: "TEST: Top-level button - WORKING", [UI]: (

WORKING Pattern: Top-level button with disabled attribute

Current count: {count}

Simulate pending state
Simulate processing state
{/* WORKING: Button always rendered at top level */} {buttonText}

This pattern works correctly! Button always rendered, uses disabled for state.

), count, pending, isProcessing, }; });