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:
// 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
// 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:
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
VerboseProblems: ❌ 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:
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:
// 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():
- Wraps everything in
batch()for performance - Iterates through all update entries
- Checks each key to determine if it's a selector or property
- Updates DOM for selectors
- Updates state for properties
- 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:
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:
// 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:
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:
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:
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:
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
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
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
function handleSubmit() {
updateAll(formState, {
isSubmitting: true,
'#submit-btn': { disabled: true },
'#status': 'Submitting...'
});
}2. Progress Updates
function updateProgress(percent) {
updateAll(app, {
progress: percent,
'#progress': { value: percent },
'#label': `${percent}%`
});
}3. Status Sync
function setStatus(status) {
updateAll(app, {
currentStatus: status,
'#status-indicator': status,
'.status-badge': { dataset: { status } }
});
}4. Bulk UI Updates
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
// 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
// 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
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
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
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
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
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
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
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:
// Trying to update state property '#count'
const app = state({ '#count': 0 });
updateAll(app, {
'#count': 5 // This updates DOM, not state!
});✅ Correct:
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:
const app = state({ user: { name: 'John' } });
updateAll(app, {
'user.name': 'Jane' // May not work as nested update
});✅ Correct:
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:
// Using updateAll for single state update
updateAll(app, {
count: 5
});✅ Correct:
// 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:
// 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!