Claude Code Plugins

Community-maintained marketplace

Feedback

reactive-ui-patterns

@codetalcott/fixiplug
0
0

Build reactive user interfaces by orchestrating FixiPlug state management and DOM manipulation. Use when creating dynamic interfaces that respond to application state changes, coordinate async operations, or implement event-driven UI updates.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name reactive-ui-patterns
description Build reactive user interfaces by orchestrating FixiPlug state management and DOM manipulation. Use when creating dynamic interfaces that respond to application state changes, coordinate async operations, or implement event-driven UI updates.
tags ui, reactive, patterns, state-management, dom-manipulation, workflow, ajax, best-practices
version 1.0.0
level intermediate
author FixiPlug Team
references stateTrackerPlugin, fixiAgentPlugin, introspectionPlugin

Reactive UI Patterns with FixiPlug

Overview

This skill teaches you to build reactive user interfaces by combining two FixiPlug plugins:

  1. State Tracker Plugin - Manages application state transitions
  2. Fixi-Agent Plugin - Provides declarative DOM manipulation with fx- attributes

The key pattern: state drives UI updates, creating a unidirectional data flow similar to modern reactive frameworks.

Core Concepts

State-Driven UI

Instead of directly manipulating the DOM, you:

  1. Update application state via api:setState
  2. Listen for state transitions via state:transition events
  3. Inject/update UI in response to state changes via api:injectFxHtml

This separates business logic (state) from presentation (DOM).

Declarative AJAX with fx- Attributes

The fixi-agent plugin allows you to inject HTML with special fx- attributes that handle AJAX requests declaratively:

  • fx-action - The URL to fetch
  • fx-method - HTTP method (GET, POST, etc.)
  • fx-target - Where to put the response
  • fx-swap - How to swap (innerHTML, outerHTML, etc.)
  • fx-trigger - What event triggers the request

Pattern 1: State-Driven Loading Indicator

Use Case: Show/hide loading indicator based on async operation state

// Step 1: Define state machine
await api:registerStateSchema({
  states: ['idle', 'loading', 'success', 'error'],
  transitions: {
    idle: ['loading'],
    loading: ['success', 'error'],
    success: ['idle'],
    error: ['idle']
  },
  initial: 'idle'
})

// Step 2: Create UI container
await api:injectFxHtml({
  html: '<div id="status-container"></div>',
  selector: '#app',
  position: 'beforeend'
})

// Step 3: Listen for state changes (in a plugin)
ctx.on('state:entered:loading', () => {
  api:injectFxHtml({
    html: '<div class="spinner">Loading...</div>',
    selector: '#status-container',
    position: 'innerHTML'
  })
})

ctx.on('state:entered:success', (event) => {
  api:injectFxHtml({
    html: `<div class="success">${event.data.message}</div>`,
    selector: '#status-container',
    position: 'innerHTML'
  })
})

// Step 4: Trigger async operation
await api:setState({ state: 'loading' })
// ... perform async work ...
await api:setState({
  state: 'success',
  data: { message: 'Data loaded!' }
})

Pattern 2: Progressive Enhancement with fx-action

Use Case: Start with a basic button, enhance it with reactive behavior

// Step 1: Inject basic interactive element
await api:injectFxHtml({
  html: `
    <button
      fx-action="/api/data"
      fx-target="#result"
      fx-swap="innerHTML"
      fx-trigger="click">
      Load Data
    </button>
    <div id="result"></div>
  `,
  selector: '#app',
  position: 'beforeend'
})

// The button now:
// 1. Listens for 'click' events automatically
// 2. Fetches /api/data when clicked
// 3. Swaps the response into #result
// 4. All without writing any JavaScript event handlers!

Pattern 3: Multi-Step Workflow with State Coordination

Use Case: Coordinate multiple async steps with state tracking

// Step 1: Define workflow states
await api:registerStateSchema({
  states: ['start', 'fetching-users', 'selecting-user', 'loading-details', 'complete'],
  transitions: {
    start: ['fetching-users'],
    'fetching-users': ['selecting-user', 'error'],
    'selecting-user': ['loading-details'],
    'loading-details': ['complete', 'error'],
    complete: ['start'],
    error: ['start']
  }
})

// Step 2: Start workflow
await api:setState({ state: 'fetching-users' })

// Step 3: Inject user list (server returns HTML with fx-action buttons)
await api:injectFxHtml({
  html: `
    <div fx-action="/api/users" fx-target="#user-list" fx-trigger="load">
      <!-- Server returns: -->
      <!-- <button fx-action="/api/user/1" fx-target="#details">User 1</button> -->
      <!-- <button fx-action="/api/user/2" fx-target="#details">User 2</button> -->
    </div>
    <div id="user-list"></div>
    <div id="details"></div>
  `,
  selector: '#app'
})

// Step 4: Listen for fx:after to advance state
ctx.on('fx:after', async (event) => {
  const currentState = await api:getCurrentState()

  if (event.cfg.action.includes('/api/users')) {
    await api:setState({ state: 'selecting-user' })
  } else if (event.cfg.action.includes('/api/user/')) {
    await api:setState({ state: 'loading-details' })
  }
})

// Step 5: Wait for completion
const result = await api:waitForState({
  state: 'complete',
  timeout: 30000
})

Pattern 4: Optimistic Updates

Use Case: Update UI immediately, roll back on error

// Step 1: Read current state
const currentData = await api:readDom({
  selector: '#item-count',
  property: 'textContent'
})

// Step 2: Optimistically update UI
await api:injectFxHtml({
  html: `<span id="item-count">${parseInt(currentData.value) + 1}</span>`,
  selector: '#item-count',
  position: 'outerHTML'
})

// Step 3: Track state for rollback
await api:setState({
  state: 'pending-save',
  data: { previousValue: currentData.value }
})

// Step 4: Trigger actual save
await api:triggerFxElement({ selector: '#save-button' })

// Step 5: Roll back on error
ctx.on('state:entered:error', async (event) => {
  if (event.data.previousValue) {
    await api:injectFxHtml({
      html: `<span id="item-count">${event.data.previousValue}</span>`,
      selector: '#item-count',
      position: 'outerHTML'
    })
  }
})

Pattern 5: Form Validation with State

Use Case: Coordinate client-side validation with server submission

// Step 1: Inject form with validation
await api:injectFxHtml({
  html: `
    <form id="user-form" fx-action="/api/users" fx-method="POST" fx-swap="none">
      <input name="email" type="email" required />
      <button type="submit">Submit</button>
    </form>
    <div id="validation-errors"></div>
  `,
  selector: '#app'
})

// Step 2: Validate before submission
ctx.on('fx:before', async (event) => {
  if (event.cfg.action === '/api/users') {
    const form = event.trigger.target.closest('form')
    if (!form.checkValidity()) {
      event.preventDefault() // Cancel request
      await api:setState({ state: 'validation-error' })
      await api:injectFxHtml({
        html: '<div class="error">Please fix form errors</div>',
        selector: '#validation-errors',
        position: 'innerHTML'
      })
    }
  }
})

// Step 3: Handle server response
ctx.on('fx:after', async (event) => {
  if (event.cfg.action === '/api/users') {
    await api:setState({ state: 'submitted' })
    await api:injectFxHtml({
      html: '<div class="success">Form submitted!</div>',
      selector: '#validation-errors',
      position: 'innerHTML'
    })
  }
})

Best Practices

1. Separation of Concerns

  • State Logic: Use state-tracker for business logic and workflow
  • UI Logic: Use fixi-agent for presentation and user interaction
  • Never mix: Don't put business logic in fx- attributes

2. Unidirectional Data Flow

  • State changes → UI updates (never UI changes → state changes directly)
  • Listen to state transitions, not DOM events
  • Use fx:events to coordinate with state

3. Error Handling

  • Always handle 'error' state in your state machine
  • Use fx:error event to catch AJAX failures
  • Provide user feedback for all error states

4. Performance

  • Use fx-swap="innerHTML" when updating container contents
  • Use fx-swap="beforeend" for appending (lists, infinite scroll)
  • Avoid unnecessary state transitions
  • Batch state updates when possible

5. Accessibility

  • Inject semantic HTML
  • Provide loading indicators for async states
  • Use ARIA attributes in injected content
  • Test keyboard navigation

Common Pitfalls

❌ Don't: Update state from UI callbacks directly

button.addEventListener('click', () => {
  api:setState({ state: 'loading' }) // NO! Creates tight coupling
})

✅ Do: Use fx- attributes and listen to fx:events

// HTML
<button fx-action="/api/data" fx-target="#result">Load</button>

// Plugin
ctx.on('fx:before', (event) => {
  if (event.cfg.action === '/api/data') {
    api:setState({ state: 'loading' })
  }
})

❌ Don't: Inject HTML with inline event handlers

api:injectFxHtml({
  html: '<button onclick="doSomething()">Click</button>' // NO!
})

✅ Do: Use fx-action for behavior

api:injectFxHtml({
  html: '<button fx-action="/api/action">Click</button>' // YES!
})

❌ Don't: Create circular state dependencies

ctx.on('state:entered:loading', () => {
  api:setState({ state: 'loaded' }) // Might trigger again!
})

✅ Do: Use conditional state transitions

ctx.on('state:entered:loading', async () => {
  const data = await fetchData()
  api:setState({ state: 'loaded', data }) // Only transition once
})

Example: Complete Todo App

Putting it all together:

// 1. Register state machine
await api:registerStateSchema({
  states: ['idle', 'loading-todos', 'ready', 'adding-todo', 'deleting-todo'],
  transitions: {
    idle: ['loading-todos'],
    'loading-todos': ['ready', 'error'],
    ready: ['adding-todo', 'deleting-todo'],
    'adding-todo': ['ready', 'error'],
    'deleting-todo': ['ready', 'error'],
    error: ['idle']
  }
})

// 2. Create app structure
await api:injectFxHtml({
  html: `
    <div id="todo-app">
      <form fx-action="/api/todos" fx-method="POST" fx-swap="none">
        <input name="title" required />
        <button type="submit">Add Todo</button>
      </form>
      <div fx-action="/api/todos" fx-target="#todo-list" fx-trigger="load"></div>
      <ul id="todo-list"></ul>
    </div>
  `,
  selector: 'body'
})

// 3. Listen for state transitions
ctx.on('state:entered:loading-todos', () => {
  api:injectFxHtml({
    html: '<li>Loading...</li>',
    selector: '#todo-list',
    position: 'innerHTML'
  })
})

ctx.on('state:entered:ready', () => {
  // List is automatically populated by fx-trigger="load"
})

// 4. Handle form submission
ctx.on('fx:after', async (event) => {
  if (event.cfg.action === '/api/todos' && event.cfg.method === 'POST') {
    // Reload list after adding
    await api:triggerFxElement({ selector: '[fx-action="/api/todos"]' })
  }
})

// 5. Start app
await api:setState({ state: 'loading-todos' })

Tools You'll Use

State Tracker Tools

  • api:getCurrentState - Check current state
  • api:setState - Transition to new state
  • api:waitForState - Wait for specific state
  • api:registerStateSchema - Define state machine
  • api:getStateHistory - Debug state transitions

Fixi-Agent Tools

  • api:injectFxHtml - Inject HTML with fx- attributes
  • api:readDom - Read current DOM state
  • api:triggerFxElement - Activate fx-action elements
  • api:getFxDocumentation - Learn fx- attribute system

Events to Listen For

  • state:transition - Any state change
  • state:entered:{stateName} - Entering specific state
  • fx:before - Before AJAX request
  • fx:after - After successful AJAX
  • fx:error - AJAX request failed

When to Use This Pattern

Good For:

  • Multi-step workflows
  • Form-heavy applications
  • CRUD operations with loading states
  • Real-time data updates
  • Progressive enhancement
  • Event-driven UIs

Not Ideal For:

  • Simple static pages
  • Performance-critical animations
  • Complex DOM manipulations
  • Large-scale SPAs (consider a full framework)

Debugging Tips

  1. Track state history:

    const history = await api:getStateHistory({ limit: 20 })
    console.log('Recent transitions:', history)
    
  2. Listen to all state transitions:

    ctx.on('state:transition', (event) => {
      console.log(`State: ${event.from} → ${event.to}`, event.data)
    })
    
  3. Debug fx- events:

    ctx.on('fx:before', (e) => console.log('Request:', e.cfg.action))
    ctx.on('fx:after', (e) => console.log('Response:', e.cfg.text))
    ctx.on('fx:error', (e) => console.error('Error:', e.error))
    
  4. Read current DOM:

    const dom = await api:readDom({ selector: '#app', property: 'outerHTML' })
    console.log('Current HTML:', dom.value)
    

Summary

Reactive UI with FixiPlug = State + Declarative DOM

  1. Define your state machine
  2. Inject HTML with fx- attributes
  3. Listen to state transitions
  4. Update UI in response to state
  5. Let fx- attributes handle user interaction

This creates a clean separation between:

  • What the UI should show (state)
  • How to show it (fx- attributes)
  • When to update (state transitions)