Skip to content

Sponsor

Sponsor

Understanding updateAll() - A Beginner's Guide

Quick Start (30 seconds)

Need to update both reactive state AND DOM elements in one call? Here's how:

js
// HTML: <div id="status"></div>

const app = state({
  count: 0,
  status: 'ready'
});

// Update both state and DOM at once
updateAll(app, {
  count: 5,                    // Updates state
  status: 'active',            // Updates state
  '#status': 'Processing...'   // Updates DOM element
});

console.log(app.count);   // 5
console.log(app.status);  // 'active'
// DOM #status shows: "Processing..."

That's it! The updateAll() function updates both reactive state properties AND DOM elements in a single batched call!

What is updateAll()?

updateAll() is a unified update function that can update both reactive state properties and DOM elements in a single call. It intelligently distinguishes between state property names and DOM selectors, applying updates to the appropriate targets.

Unified updates:

  • Updates reactive state properties
  • Updates DOM elements via CSS selectors
  • Batches all updates automatically
  • Single function for state + DOM changes
  • Intelligent selector detection

Think of it as a universal remote control - one tool to control both your data (state) and your display (DOM).

Syntax

js
// Using the shortcut
updateAll(state, updates)

// Using the full namespace
ReactiveUtils.updateAll(state, updates)

// Global shortcut
updateAll(state, updates)

All styles are valid! Choose whichever you prefer:

  • Shortcut style (updateAll()) - Clean and concise
  • Namespace style (ReactiveUtils.updateAll()) - Explicit and clear
  • Global style (also updateAll()) - Available globally

Parameters:

  • state - The reactive state object (required)
  • updates - Object with keys as state properties or DOM selectors, and values as the new values (required)

Returns:

  • The updated state object

Why Does This Exist?

The Problem with Separate State and DOM Updates

Let's say you need to update both reactive state and DOM elements:

javascript
const app = state({
  status: 'idle',
  progress: 0,
  message: ''
});

// User triggers an action
function startProcess() {
  // Update state
  app.status = 'processing';
  app.progress = 0;
  app.message = 'Starting...';

  // Update DOM elements separately
  document.getElementById('status').textContent = 'Processing';
  document.getElementById('progress').value = 0;
  document.querySelector('.message').textContent = 'Starting...';
}

// Later, during processing
function updateProgress(percent) {
  // Update state
  app.progress = percent;

  // Update DOM separately
  document.getElementById('progress').value = percent;
  document.querySelector('.percent').textContent = `${percent}%`;
}

This works, but it's verbose and error-prone:

What's the Real Issue?

Separate Updates:
┌──────────────────┐
│ Update state     │
│ app.status = ... │
│ app.progress = ..│
└────────┬─────────┘


┌──────────────────┐
│ Update DOM       │
│ getElementById() │
│ querySelector()  │
└────────┬─────────┘


  Two separate steps
  Not batched together
  Error-prone
  Verbose

Problems: ❌ State and DOM updates are separate ❌ Repetitive code for each update ❌ Easy to forget DOM updates ❌ Updates not batched together automatically ❌ Mixing state logic with DOM manipulation ❌ Hard to see what's being updated

Why This Becomes a Problem:

When you need to:

  • Update both state and DOM in sync
  • Reduce boilerplate code
  • Ensure consistency between state and UI
  • Batch all updates for performance
  • Simplify update logic

The Solution with updateAll()

When you use updateAll(), everything happens in one call:

javascript
const app = state({
  status: 'idle',
  progress: 0,
  message: ''
});

// Update both state and DOM at once
function startProcess() {
  updateAll(app, {
    status: 'processing',           // State
    progress: 0,                    // State
    message: 'Starting...',         // State
    '#status': 'Processing',        // DOM
    '#progress': { value: 0 },      // DOM
    '.message': 'Starting...'       // DOM
  });
}

// Later
function updateProgress(percent) {
  updateAll(app, {
    progress: percent,                      // State
    '#progress': { value: percent },        // DOM
    '.percent': `${percent}%`               // DOM
  });
}

What Just Happened?

Unified Updates:
┌─────────────────────┐
│  updateAll(state, { │
│    state props,     │
│    DOM selectors    │
│  })                 │
└──────────┬──────────┘


    Batched together!

    ┌──────┴──────┐
    │             │
    ▼             ▼
┌────────┐   ┌────────┐
│ State  │   │  DOM   │
│ update │   │ update │
└────────┘   └────────┘


  Single call!
  All batched!
  Clean code!

With updateAll():

  • State and DOM updates in one call
  • Automatically batched for performance
  • Less boilerplate code
  • Clear intent
  • Consistent updates

Benefits: ✅ Update state and DOM in one call ✅ Automatically batched updates ✅ Less code, cleaner syntax ✅ Ensures state and UI consistency ✅ Intelligent selector detection ✅ Performance optimized

Mental Model

Think of updateAll() like a universal remote control:

Separate Remotes (Old Way):
┌──────────────┐  ┌──────────────┐
│ TV Remote    │  │ Sound Remote │
│ (State)      │  │ (DOM)        │
└──────┬───────┘  └──────┬───────┘
       │                 │
       ▼                 ▼
  Change TV       Change Sound
  (2 devices,     (2 separate
   2 remotes)      actions)


Universal Remote (updateAll):
┌─────────────────────────┐
│  Universal Remote       │
│  ┌───────────────────┐  │
│  │ TV + Sound        │  │
│  │ (State + DOM)     │  │
│  └───────────────────┘  │
└────────────┬────────────┘


    Change Both at Once
    (1 remote, 1 action)

Key Insight: Just like a universal remote that controls multiple devices with one button press, updateAll() updates both state and DOM with one function call.

How Does It Work?

The Magic: Selector Detection

When you call updateAll(), here's what happens behind the scenes:

javascript
// What you write:
updateAll(app, {
  count: 5,
  '#status': 'Ready'
});

// What actually happens (simplified):
function updateAll(state, updates) {
  return batch(() => {
    Object.entries(updates).forEach(([key, value]) => {
      // Check if key is a DOM selector
      if (key.startsWith('#') ||      // ID selector
          key.startsWith('.') ||      // Class selector
          key.includes('[') ||        // Attribute selector
          key.includes('>')) {        // Child selector
        // It's a DOM selector
        updateDOMElements(key, value);
      } else {
        // It's a state property
        state[key] = value;
      }
    });
    return state;
  });
}

In other words: updateAll():

  1. Wraps everything in batch() for performance
  2. Iterates through all update entries
  3. Checks each key to determine if it's a selector or property
  4. Updates DOM for selectors
  5. Updates state for properties
  6. Returns the state

Under the Hood

updateAll(state, updates):
┌──────────────────┐
│ batch(() => {    │
└────────┬─────────┘


┌──────────────────┐
│ For each entry   │
└────────┬─────────┘

    ┌────┴────┐
    │         │
    ▼         ▼
Is selector? YES    NO
    │         │
    ▼         ▼
Update DOM  Update State
    │         │
    └────┬────┘


┌──────────────────┐
│ })               │
│ Return state     │
└──────────────────┘

What happens:

1️⃣ Batches all updates for performance 2️⃣ Iterates through update object 3️⃣ Detects selectors vs properties 4️⃣ Updates DOM or state accordingly 5️⃣ Returns the state object

Basic Usage

Updating Only State

Use like a batch update:

js
const app = state({ count: 0, name: 'App' });

updateAll(app, {
  count: 10,
  name: 'MyApp'
});

console.log(app.count);  // 10
console.log(app.name);   // 'MyApp'

Updating Only DOM

Use with CSS selectors:

js
// HTML: <div id="status"></div>
//       <div class="message"></div>

updateAll(app, {
  '#status': 'Ready',
  '.message': 'Welcome!'
});

// #status shows: "Ready"
// .message shows: "Welcome!"

Mixed Updates

Update both state and DOM:

js
const app = state({ count: 0 });

updateAll(app, {
  count: 5,                     // State
  '#counter': '5',              // DOM
  '#status': 'Updated'          // DOM
});

console.log(app.count);  // 5
// #counter shows: "5"
// #status shows: "Updated"

State Updates vs DOM Updates

State Property Updates

Any key that doesn't look like a selector updates state:

js
updateAll(app, {
  count: 10,           // State property
  name: 'Test',        // State property
  active: true,        // State property
  'user.name': 'John'  // Nested state property (if supported)
});

DOM Selector Updates

Keys that look like CSS selectors update DOM:

js
updateAll(app, {
  '#myId': 'Text',              // ID selector
  '.myClass': 'Text',           // Class selector
  '[data-value]': 'Text',       // Attribute selector
  'div > span': 'Text'          // Child selector
});

DOM Update Values

Different value types for DOM updates:

js
updateAll(app, {
  // String: Sets textContent
  '#text': 'Hello',

  // Object: Sets properties
  '#input': { value: '123', disabled: true },

  // Object with style: Sets styles
  '#box': { style: { color: 'red', fontSize: '16px' } },

  // Object with dataset: Sets data attributes
  '#item': { dataset: { id: '5', name: 'Item' } }
});

Mixed Updates

Updating State and Multiple DOM Elements

js
const form = state({
  email: '',
  password: '',
  isValid: false
});

function submitForm() {
  updateAll(form, {
    // State updates
    email: emailInput.value,
    password: passwordInput.value,
    isValid: true,

    // DOM updates
    '#submit-btn': { disabled: true, textContent: 'Submitting...' },
    '#status': 'Validating...',
    '.error': { style: { display: 'none' } }
  });
}

Progressive Updates

js
const progress = state({ percent: 0, step: 1 });

function updateProgress(newPercent, newStep, message) {
  updateAll(progress, {
    // State
    percent: newPercent,
    step: newStep,

    // DOM
    '#progress-bar': { value: newPercent },
    '#percent-text': `${newPercent}%`,
    '#step-text': `Step ${newStep}`,
    '#message': message
  });
}

When to Use updateAll()

✅ Good Use Cases

1. Form Handling

js
function handleSubmit() {
  updateAll(formState, {
    isSubmitting: true,
    '#submit-btn': { disabled: true },
    '#status': 'Submitting...'
  });
}

2. Progress Updates

js
function updateProgress(percent) {
  updateAll(app, {
    progress: percent,
    '#progress': { value: percent },
    '#label': `${percent}%`
  });
}

3. Status Sync

js
function setStatus(status) {
  updateAll(app, {
    currentStatus: status,
    '#status-indicator': status,
    '.status-badge': { dataset: { status } }
  });
}

4. Bulk UI Updates

js
function showError(message) {
  updateAll(app, {
    hasError: true,
    errorMessage: message,
    '#error-box': { style: { display: 'block' } },
    '#error-text': message,
    '#submit-btn': { disabled: true }
  });
}

❌ Not Needed

1. State-Only Updates

js
// Don't use updateAll for state-only
updateAll(app, {
     count: 5,
     name: 'Test'
   });

// Use batch or direct assignment
batch(() => {
     app.count = 5;
     app.name = 'Test';
   });

2. DOM-Only Updates

js
// Don't use updateAll for DOM-only
updateAll(app, {
     '#text': 'Hello',
     '#value': '123'
   });

// Use direct DOM manipulation or bindings
✅ document.getElementById('text').textContent = 'Hello';
   document.getElementById('value').value = '123';

Real-World Examples

Example 1: Login Form

js
const loginForm = state({
  email: '',
  password: '',
  isSubmitting: false,
  error: null
});

async function handleLogin() {
  updateAll(loginForm, {
    // State
    isSubmitting: true,
    error: null,

    // DOM
    '#login-btn': { disabled: true, textContent: 'Logging in...' },
    '#error-box': { style: { display: 'none' } }
  });

  try {
    const response = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: loginForm.email,
        password: loginForm.password
      })
    });

    if (response.ok) {
      updateAll(loginForm, {
        // State
        isSubmitting: false,

        // DOM
        '#login-btn': { textContent: 'Success!' },
        '#status': { textContent: 'Redirecting...', style: { color: 'green' } }
      });

      setTimeout(() => {
        window.location.href = '/dashboard';
      }, 1000);
    } else {
      const error = await response.json();

      updateAll(loginForm, {
        // State
        isSubmitting: false,
        error: error.message,

        // DOM
        '#login-btn': { disabled: false, textContent: 'Login' },
        '#error-box': { style: { display: 'block' } },
        '#error-text': error.message
      });
    }
  } catch (error) {
    updateAll(loginForm, {
      // State
      isSubmitting: false,
      error: 'Network error',

      // DOM
      '#login-btn': { disabled: false, textContent: 'Login' },
      '#error-box': { style: { display: 'block' } },
      '#error-text': 'Network error. Please try again.'
    });
  }
}

Example 2: File Upload Progress

js
const uploadState = state({
  file: null,
  progress: 0,
  status: 'idle',
  uploadedBytes: 0,
  totalBytes: 0
});

function uploadFile(file) {
  const formData = new FormData();
  formData.append('file', file);

  const xhr = new XMLHttpRequest();

  xhr.upload.addEventListener('progress', (e) => {
    if (e.lengthComputable) {
      const percent = Math.round((e.loaded / e.total) * 100);

      updateAll(uploadState, {
        // State
        progress: percent,
        uploadedBytes: e.loaded,
        totalBytes: e.total,
        status: 'uploading',

        // DOM
        '#progress-bar': { value: percent },
        '#progress-text': `${percent}%`,
        '#bytes-text': `${formatBytes(e.loaded)} / ${formatBytes(e.total)}`,
        '#status-text': 'Uploading...'
      });
    }
  });

  xhr.addEventListener('load', () => {
    if (xhr.status === 200) {
      updateAll(uploadState, {
        // State
        progress: 100,
        status: 'complete',

        // DOM
        '#progress-bar': { value: 100 },
        '#progress-text': '100%',
        '#status-text': { textContent: 'Upload complete!', style: { color: 'green' } }
      });
    }
  });

  xhr.open('POST', '/api/upload');
  xhr.send(formData);
}

Example 3: Multi-Step Wizard

js
const wizardState = state({
  currentStep: 1,
  totalSteps: 4,
  canGoBack: false,
  canGoNext: true,
  isComplete: false
});

function goToStep(step) {
  const canGoBack = step > 1;
  const canGoNext = step < wizardState.totalSteps;
  const isComplete = step === wizardState.totalSteps;

  updateAll(wizardState, {
    // State
    currentStep: step,
    canGoBack: canGoBack,
    canGoNext: canGoNext,
    isComplete: isComplete,

    // DOM
    '#step-indicator': `Step ${step} of ${wizardState.totalSteps}`,
    '#progress-bar': { value: (step / wizardState.totalSteps) * 100 },
    '#back-btn': { disabled: !canGoBack },
    '#next-btn': {
      disabled: !canGoNext,
      textContent: isComplete ? 'Finish' : 'Next'
    },
    '.step': { style: { display: 'none' } },
    [`#step-${step}`]: { style: { display: 'block' } }
  });
}

Example 4: Shopping Cart

js
const cartState = state({
  items: [],
  subtotal: 0,
  tax: 0,
  total: 0,
  itemCount: 0
});

function addToCart(product) {
  cartState.items.push(product);

  const subtotal = cartState.items.reduce((sum, item) => sum + item.price, 0);
  const tax = subtotal * 0.08;
  const total = subtotal + tax;

  updateAll(cartState, {
    // State
    subtotal: subtotal,
    tax: tax,
    total: total,
    itemCount: cartState.items.length,

    // DOM
    '#cart-count': cartState.items.length,
    '#subtotal': `$${subtotal.toFixed(2)}`,
    '#tax': `$${tax.toFixed(2)}`,
    '#total': `$${total.toFixed(2)}`,
    '#cart-icon': {
      dataset: { count: cartState.items.length },
      style: { animation: 'bounce 0.5s' }
    }
  });
}

Common Patterns

Pattern: Form Status Updates

js
function setFormStatus(status, message) {
  const statusConfig = {
    loading: { color: 'blue', text: 'Loading...' },
    success: { color: 'green', text: 'Success!' },
    error: { color: 'red', text: 'Error!' }
  };

  const config = statusConfig[status];

  updateAll(formState, {
    status: status,
    statusMessage: message,
    '#status': {
      textContent: config.text,
      style: { color: config.color }
    },
    '#message': message
  });
}

Pattern: Batch DOM and State Reset

js
function resetApp() {
  updateAll(app, {
    // State
    count: 0,
    name: '',
    active: false,

    // DOM
    '#count': '0',
    '#name-input': { value: '' },
    '#status': 'Ready',
    '.active-indicator': { style: { display: 'none' } }
  });
}

Pattern: Progressive Enhancement

js
function enhanceWithProgress(operation) {
  return async function(...args) {
    updateAll(app, {
      isLoading: true,
      '#loader': { style: { display: 'block' } },
      '#content': { style: { opacity: '0.5' } }
    });

    try {
      const result = await operation(...args);

      updateAll(app, {
        isLoading: false,
        '#loader': { style: { display: 'none' } },
        '#content': { style: { opacity: '1' } }
      });

      return result;
    } catch (error) {
      updateAll(app, {
        isLoading: false,
        error: error.message,
        '#loader': { style: { display: 'none' } },
        '#error': { textContent: error.message, style: { display: 'block' } }
      });
    }
  };
}

Common Pitfalls

Pitfall #1: Confusing State Properties with Selectors

Wrong:

js
// Trying to update state property '#count'
const app = state({ '#count': 0 });

updateAll(app, {
  '#count': 5  // This updates DOM, not state!
});

Correct:

js
const app = state({ count: 0 });

updateAll(app, {
  count: 5,      // State
  '#count': '5'  // DOM
});

Why? Keys starting with #, ., or containing [, > are treated as DOM selectors.

Pitfall #2: Expecting Nested State Updates

Wrong:

js
const app = state({ user: { name: 'John' } });

updateAll(app, {
  'user.name': 'Jane'  // May not work as nested update
});

Correct:

js
updateAll(app, {
  user: { ...app.user, name: 'Jane' }
});

Why? updateAll() treats keys as-is. Nested path notation may not be supported.

Pitfall #3: Over-Using for Simple Updates

Wrong:

js
// Using updateAll for single state update
updateAll(app, {
  count: 5
});

Correct:

js
// Just use direct assignment
app.count = 5;

Why? updateAll() is best for mixed state + DOM updates, not simple state changes.

Summary

What is updateAll()?

updateAll() is a unified update function that updates both reactive state properties and DOM elements in a single batched call.

Why use updateAll()?

  • Update state and DOM together
  • Automatically batched for performance
  • Less boilerplate code
  • Ensures consistency
  • Clear, declarative syntax
  • One call for everything

Key Points to Remember:

1️⃣ Dual purpose - Updates state and DOM 2️⃣ Selector detection - Intelligently distinguishes selectors from properties 3️⃣ Auto-batched - All updates happen in one batch 4️⃣ Mixed updates - Perfect for state + UI sync 5️⃣ Returns state - Chainable if needed

Mental Model: Think of updateAll() as a universal remote - one tool to control both your data (state) and your display (DOM).

Quick Reference:

js
// State only
updateAll(app, {
  count: 5,
  name: 'Test'
});

// DOM only
updateAll(app, {
  '#status': 'Ready',
  '.message': 'Hello'
});

// Mixed
updateAll(app, {
  count: 5,                    // State
  '#counter': '5',             // DOM
  '#status': 'Updated'         // DOM
});

// Complex DOM updates
updateAll(app, {
  progress: 75,
  '#progress-bar': { value: 75 },
  '#label': { textContent: '75%', style: { color: 'green' } }
});

Remember: updateAll() is your unified update tool for keeping state and UI in perfect sync. Use it when you need to update both reactive state and DOM elements in one clean, batched call!