Understanding pause() - A Beginner's Guide
Quick Start (30 seconds)
Need to temporarily stop effects from running while you make changes? Here's how:
const user = state({
firstName: 'John',
lastName: 'Doe',
age: 25
});
effect(() => {
console.log(`${user.firstName} ${user.lastName}, age ${user.age}`);
});
// Logs: "John Doe, age 25"
// Pause reactivity
pause();
// Make changes - no effects run
user.firstName = 'Jane'; // No log
user.lastName = 'Smith'; // No log
user.age = 30; // No log
// Resume reactivity
resume(true); // true = flush queued effects
// Logs once: "Jane Smith, age 30"That's it! The pause() function temporarily stops effects from running until you call resume()!
What is pause()?
pause() is a manual control function that temporarily disables reactivity. When paused, state changes are tracked but effects don't run until you resume reactivity.
Pausing reactivity:
- Stops effects from running
- Queues effect executions
- Allows manual control over when effects run
- Must be paired with
resume()to re-enable reactivity
Think of it as pressing the pause button on a video player - everything stops until you press play again.
Syntax
// Using the shortcut
pause()
// Using the full namespace
ReactiveUtils.pause()Both styles are valid! Choose whichever you prefer:
- Shortcut style (
pause()) - Clean and concise - Namespace style (
ReactiveUtils.pause()) - Explicit and clear
Parameters:
- None
Returns:
- Nothing (undefined)
Why Does This Exist?
The Problem with Continuous Reactivity
Let's say you need to make many changes over time:
// Create reactive state with an effect
const dashboard = state({
data: [],
filters: {},
sorting: 'asc',
page: 1,
isLoading: false
});
effect(() => {
// Expensive: fetch and render data
console.log('Fetching data with filters:', dashboard.filters);
fetchAndRenderData(dashboard);
});
// User is configuring multiple filters
dashboard.filters.category = 'electronics'; // Effect runs!
dashboard.filters.minPrice = 100; // Effect runs!
dashboard.filters.maxPrice = 500; // Effect runs!
dashboard.filters.brand = 'Apple'; // Effect runs!
dashboard.sorting = 'desc'; // Effect runs!This works, but it's inefficient. The effect runs 5 times while the user is still configuring!
What's the Real Issue?
Normal Updates Flow:
┌─────────────────────┐
│ filters.category = │
│ 'electronics' │
└──────────┬──────────┘
│
▼
Fetch data! 🌐
(Wasted!)
│
▼
┌─────────────────────┐
│ filters.minPrice = │
│ 100 │
└──────────┬──────────┘
│
▼
Fetch data! 🌐
(Wasted!)
│
▼
┌─────────────────────┐
│ filters.maxPrice = │
│ 500 │
└──────────┬──────────┘
│
▼
Fetch data! 🌐
(Wasted!)
│
▼
Many executions!
Very inefficient!Problems: ❌ Effects run during intermediate configuration states ❌ Expensive operations happen multiple times unnecessarily ❌ No manual control over when effects run ❌ Can't defer reactivity until configuration is complete ❌ Performance suffers with complex multi-step updates ❌ API calls or DOM updates happen prematurely
Why This Becomes a Problem:
Sometimes you need to:
- Make many related changes before triggering effects
- Import/restore data without triggering effects during the process
- Configure complex state and only trigger effects when done
- Have fine-grained control over reactivity timing
The Solution with pause()
When you use pause(), you can defer all effects until you're ready:
// Same state and effect setup
const dashboard = state({
data: [],
filters: {},
sorting: 'asc',
page: 1
});
effect(() => {
console.log('Fetching data with filters:', dashboard.filters);
fetchAndRenderData(dashboard);
});
// Pause reactivity before making changes
pause();
// Make all changes - no effects run yet
dashboard.filters.category = 'electronics'; // No effect
dashboard.filters.minPrice = 100; // No effect
dashboard.filters.maxPrice = 500; // No effect
dashboard.filters.brand = 'Apple'; // No effect
dashboard.sorting = 'desc'; // No effect
// Resume and flush - effect runs once
resume(true);
// Logs once: "Fetching data with filters: {category: 'electronics', ...}"What Just Happened?
Paused Updates Flow:
┌──────────────────┐
│ pause() │
└────────┬─────────┘
│
▼
Reactivity OFF
│
▼
┌─────────────────┐
│ filters.category│ ← Change tracked
└────────┬────────┘
│
▼
┌─────────────────┐
│ filters.minPrice│ ← Change tracked
└────────┬────────┘
│
▼
┌─────────────────┐
│ filters.maxPrice│ ← Change tracked
└────────┬────────┘
│
▼
┌─────────────────┐
│ resume(true) │
└────────┬────────┘
│
▼
Reactivity ON
Flush effects
│
▼
Effect runs
ONCE! 🎯With pause() and resume():
- Reactivity is manually controlled
- All changes are tracked but effects are deferred
- Effects run once when you call
resume() - Perfect for complex multi-step operations
- Full control over timing
Benefits: ✅ Manual control over when effects run ✅ Perfect for multi-step configuration ✅ Prevents premature expensive operations ✅ Effects run once when you're ready ✅ Great for data import/restore scenarios ✅ Fine-grained reactivity control
Mental Model
Think of pause() like a traffic light controller:
Normal Reactivity (Green Light):
┌──────────────┐
│ State change │
└──────┬───────┘
│
▼
🟢 GO!
│
▼
Effect runs
immediately
Paused Reactivity (Red Light):
┌──────────────┐
│ pause() │
└──────┬───────┘
│
▼
🔴 STOP!
│
▼
┌──────────────┐
│ State change │ ← Tracked
└──────┬───────┘
│
▼
┌──────────────┐
│ State change │ ← Tracked
└──────┬───────┘
│
▼
┌──────────────┐
│ State change │ ← Tracked
└──────┬───────┘
│
▼
┌──────────────┐
│ resume() │
└──────┬───────┘
│
▼
🟢 GO!
│
▼
All effects
run now!Key Insight: Just like a traffic light controller that can stop and start traffic flow, pause() and resume() control the flow of reactivity - stopping effects temporarily and resuming them when you're ready.
How Does It Work?
The Magic: Batch Depth Counter
When you call pause(), here's what happens behind the scenes:
// What you write:
pause();
user.firstName = 'Jane';
user.lastName = 'Smith';
resume(true);
// What actually happens (simplified):
// 1. Increment batch depth counter
batchDepth = 1; // pause() does batchDepth++
// 2. Make changes
user.firstName = 'Jane'; // Effect queued, not run
user.lastName = 'Smith'; // Effect queued, not run
// 3. Decrement batch depth counter and flush
batchDepth = 0; // resume() does batchDepth--
flush(); // resume(true) flushes queued effectsIn other words: pause() increments the same counter that batch() uses:
- Increments counter (reactivity paused)
- While counter > 0, effects are queued
resume()decrements counter- When counter reaches 0 and flush=true, effects run
Under the Hood
pause() implementation:
pause() {
batchDepth++;
}
resume(flush) implementation:
resume(flush) {
batchDepth = Math.max(0, batchDepth - 1);
if (flush && batchDepth === 0) {
runAllQueuedEffects();
}
}What happens:
1️⃣ pause() increments the batch depth counter 2️⃣ While paused (counter > 0), effects are queued 3️⃣ resume() decrements the counter 4️⃣ If flush=true and counter=0, all queued effects run
Basic Usage
Pause and Resume
The simplest way to use pause():
const app = state({ count: 0 });
effect(() => {
console.log('Count:', app.count);
});
// Logs: "Count: 0"
// Pause reactivity
pause();
// Make changes - no effects run
app.count = 1; // No log
app.count = 2; // No log
app.count = 3; // No log
// Resume and flush
resume(true);
// Logs: "Count: 3"Resume Without Flushing
You can resume without running effects:
pause();
app.count = 10;
resume(false); // Resume but don't flush
// No log yet
// Effects will run on next change
app.count = 11;
// Logs: "Count: 11"Multiple Pause/Resume Cycles
pause();
app.count = 1;
resume(true); // Logs: "Count: 1"
pause();
app.count = 2;
resume(true); // Logs: "Count: 2"
pause();
app.count = 3;
resume(true); // Logs: "Count: 3"pause() vs batch()
Both pause() and batch() defer effects, but they're used differently:
When to Use batch()
Use batch() when you have a specific block of code to execute:
✅ You know exactly what updates to make ✅ All updates are in one function ✅ Automatic resume after function completes
// batch() - automatic
batch(() => {
user.firstName = 'Jane';
user.lastName = 'Smith';
user.age = 30;
}); // Automatically resumes and flushes hereWhen to Use pause()
Use pause() when you need manual control over reactivity:
✅ Updates span multiple functions ✅ Updates happen over time ✅ Need manual control of resume timing ✅ Complex multi-step processes
// pause() - manual
pause();
// Updates can span multiple functions
updateUserName();
updateUserAge();
updateUserAddress();
// You control when to resume
resume(true);Quick Comparison
// ✅ batch() - Automatic, scoped
batch(() => {
user.name = 'John';
user.age = 30;
}); // Auto-resumes here
// ✅ pause() - Manual, flexible
pause();
user.name = 'John';
user.age = 30;
resume(true); // Manual resumeBoth use the same underlying mechanism (batch depth counter), but batch() is automatic while pause()/resume() is manual.
When to Use pause()
✅ Good Use Cases
1. Data Import/Restore
function importData(data) {
pause();
// Import all data without triggering effects
app.users = data.users;
app.posts = data.posts;
app.comments = data.comments;
app.settings = data.settings;
resume(true); // Effects run once with complete data
}2. Multi-Step Configuration
function configureFilters(config) {
pause();
dashboard.filters = {};
if (config.category) dashboard.filters.category = config.category;
if (config.priceRange) {
dashboard.filters.minPrice = config.priceRange.min;
dashboard.filters.maxPrice = config.priceRange.max;
}
if (config.brands) dashboard.filters.brands = config.brands;
resume(true);
}3. Bulk Updates Across Functions
function updateUserProfile() {
pause();
updateBasicInfo();
updateContactInfo();
updatePreferences();
updateSettings();
resume(true);
}4. Complex State Initialization
function initializeApp() {
pause();
loadUserData();
loadAppSettings();
loadCachedData();
setupDefaultValues();
restoreUserPreferences();
resume(true); // UI updates once with everything ready
}❌ Not Needed
1. Simple Updates
// Don't use pause for simple cases
❌ pause();
user.name = 'John';
resume(true);
// Just use batch() instead
✅ batch(() => {
user.name = 'John';
});2. Single-Function Updates
// If all updates are in one function, use batch()
❌ function update() {
pause();
user.name = 'John';
user.age = 30;
resume(true);
}
// batch() is cleaner
✅ function update() {
batch(() => {
user.name = 'John';
user.age = 30;
});
}Real-World Examples
Example 1: Form Data Import
const form = state({
firstName: '',
lastName: '',
email: '',
phone: '',
address: '',
city: '',
country: ''
});
effect(() => {
// Expensive: Validate and update UI for all fields
console.log('Validating form...');
validateAllFields(form);
updateFormUI(form);
});
function loadSavedData() {
const saved = localStorage.getItem('formData');
if (!saved) return;
const data = JSON.parse(saved);
// Pause before importing
pause();
// Import all saved fields
Object.keys(data).forEach(key => {
form[key] = data[key];
});
// Resume and validate once
resume(true);
// Logs once: "Validating form..."
}Example 2: Dashboard Filter Configuration
const dashboard = state({
filters: {
dateRange: null,
categories: [],
status: 'all',
search: ''
},
sorting: 'date',
page: 1
});
effect(() => {
// Expensive: Fetch data from API
console.log('Fetching dashboard data...');
fetchDashboardData(dashboard);
});
function applyFilterPreset(preset) {
pause();
// Apply all preset filters
dashboard.filters.dateRange = preset.dateRange;
dashboard.filters.categories = preset.categories;
dashboard.filters.status = preset.status;
dashboard.filters.search = preset.search;
dashboard.sorting = preset.sorting;
dashboard.page = 1;
resume(true);
// API call happens once with all filters
}
// Usage
applyFilterPreset({
dateRange: 'last-30-days',
categories: ['electronics', 'clothing'],
status: 'active',
search: '',
sorting: 'popularity'
});Example 3: Game State Initialization
const game = state({
level: 1,
score: 0,
lives: 3,
powerUps: [],
enemies: [],
player: { x: 0, y: 0, health: 100 }
});
effect(() => {
// Expensive: Render entire game scene
console.log('Rendering game...');
renderGame(game);
});
function loadSaveGame(saveData) {
pause();
// Restore all game state
game.level = saveData.level;
game.score = saveData.score;
game.lives = saveData.lives;
game.powerUps = saveData.powerUps;
game.enemies = saveData.enemies;
game.player = saveData.player;
resume(true);
// Game renders once with complete state
}
function startNewGame() {
pause();
// Reset all state
game.level = 1;
game.score = 0;
game.lives = 3;
game.powerUps = [];
game.enemies = generateEnemies(1);
game.player = { x: 50, y: 50, health: 100 };
resume(true);
// Game renders once with initial state
}Example 4: Multi-Step User Onboarding
const onboarding = state({
step: 1,
userData: {},
preferences: {},
settings: {},
isComplete: false
});
effect(() => {
// Update UI for current onboarding step
console.log(`Step ${onboarding.step}: ${getStepTitle(onboarding.step)}`);
renderOnboardingStep(onboarding);
});
function skipOnboarding() {
pause();
// Jump to end with defaults
onboarding.step = 5;
onboarding.userData = { name: 'Guest' };
onboarding.preferences = getDefaultPreferences();
onboarding.settings = getDefaultSettings();
onboarding.isComplete = true;
resume(true);
// UI updates once showing completion
}
function completeStep(stepData) {
pause();
// Update step data
switch (onboarding.step) {
case 1:
onboarding.userData = { ...onboarding.userData, ...stepData };
break;
case 2:
onboarding.preferences = { ...onboarding.preferences, ...stepData };
break;
case 3:
onboarding.settings = { ...onboarding.settings, ...stepData };
break;
}
// Move to next step
onboarding.step++;
if (onboarding.step > 4) {
onboarding.isComplete = true;
}
resume(true);
// UI updates to next step
}Common Patterns
Pattern: Pause During Async Operations
async function loadData() {
pause();
try {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts);
app.user = user;
app.posts = posts;
app.comments = comments;
resume(true); // Update UI once with all data
} catch (error) {
resume(false); // Resume without flushing
app.error = error;
}
}Pattern: Conditional Resume
function updateWithValidation(updates) {
pause();
Object.assign(state, updates);
const isValid = validate(state);
if (isValid) {
resume(true); // Flush if valid
} else {
resume(false); // Don't flush if invalid
// Revert changes
Object.assign(state, originalState);
}
}Pattern: Nested Pause/Resume
function complexUpdate() {
pause();
updateSection1();
pause(); // Nested pause
updateSection2();
updateSection3();
resume(false); // Inner resume (no flush)
updateSection4();
resume(true); // Outer resume (flush all)
}Pattern: Try-Finally for Safe Resume
function safeUpdate(updates) {
pause();
try {
// Make updates
Object.assign(state, updates);
} finally {
// Always resume, even if errors occur
resume(true);
}
}Common Pitfalls
Pitfall #1: Forgetting to Resume
❌ Wrong:
pause();
user.name = 'John';
user.age = 30;
// Forgot to resume! Reactivity is stuck!
// Later code won't trigger effects either
user.email = 'john@example.com'; // Still paused!✅ Correct:
pause();
user.name = 'John';
user.age = 30;
resume(true); // Always resume!Tip: Use try-finally to ensure resume is called:
pause();
try {
user.name = 'John';
user.age = 30;
} finally {
resume(true); // Always called
}Pitfall #2: Mismatched Pause/Resume Counts
❌ Wrong:
pause();
pause(); // Paused twice
user.name = 'John';
resume(true); // Only resumed once - still paused!
user.age = 30; // Won't trigger effects (still paused)✅ Correct:
pause();
pause(); // Paused twice
user.name = 'John';
resume(true); // Resume once
resume(true); // Resume again - now fully resumed
user.age = 30; // Will trigger effectsWhy? Each pause() increments a counter. You need the same number of resume() calls to fully resume.
Pitfall #3: Not Using batch() for Simple Cases
❌ Wrong:
// Overusing pause() for simple scoped updates
function update() {
pause();
user.name = 'John';
user.age = 30;
resume(true);
}✅ Correct:
// Use batch() instead
function update() {
batch(() => {
user.name = 'John';
user.age = 30;
});
}Why? batch() automatically resumes and is safer for scoped updates.
Pitfall #4: Assuming resume(false) Discards Changes
❌ Wrong:
pause();
user.name = 'John';
resume(false); // Changes are still there!
console.log(user.name); // "John" (not reverted)✅ Correct:
pause();
const original = { ...user };
user.name = 'John';
if (!isValid(user)) {
// Manually revert if needed
Object.assign(user, original);
}
resume(false);Why? resume(false) just doesn't flush effects - it doesn't undo changes.
Summary
What is pause()?
pause() is a manual control function that temporarily disables reactivity, preventing effects from running until you call resume().
Why use pause() instead of batch()?
- Manual control over reactivity timing
- Updates span multiple functions or time periods
- Complex multi-step processes
- Data import/restore scenarios
- Need fine-grained control
Key Points to Remember:
1️⃣ Manual control - You decide when to pause and resume 2️⃣ Must resume - Always pair with resume(), preferably in try-finally 3️⃣ Uses batch counter - Same mechanism as batch(), but manual 4️⃣ Can nest - Multiple pause calls require multiple resumes 5️⃣ Flush optional - resume(true) flushes, resume(false) doesn't
Mental Model: Think of pause() as a traffic light controller - it stops the flow of reactivity (red light) until you resume (green light).
Quick Reference:
// Basic usage
pause();
user.firstName = 'Jane';
user.lastName = 'Smith';
resume(true); // Flush effects
// Safe usage with try-finally
pause();
try {
user.name = 'John';
user.age = 30;
} finally {
resume(true);
}
// Nested pausing
pause();
updateSection1();
pause();
updateSection2();
resume(false);
updateSection3();
resume(true);Remember: pause() gives you manual control over reactivity. Use it when you need to make updates across multiple functions or over time, and always remember to call resume() when done!