Skip to content

Sponsor

Sponsor

Understanding pause() - A Beginner's Guide

Quick Start (30 seconds)

Need to temporarily stop effects from running while you make changes? Here's how:

js
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

js
// 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:

javascript
// 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:

javascript
// 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:

javascript
// 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 effects

In other words: pause() increments the same counter that batch() uses:

  1. Increments counter (reactivity paused)
  2. While counter > 0, effects are queued
  3. resume() decrements counter
  4. 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():

js
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:

js
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

js
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

js
// batch() - automatic
batch(() => {
  user.firstName = 'Jane';
  user.lastName = 'Smith';
  user.age = 30;
}); // Automatically resumes and flushes here

When 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

js
// pause() - manual
pause();

// Updates can span multiple functions
updateUserName();
updateUserAge();
updateUserAddress();

// You control when to resume
resume(true);

Quick Comparison

javascript
// ✅ 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 resume

Both 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

js
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

js
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

js
function updateUserProfile() {
  pause();

  updateBasicInfo();
  updateContactInfo();
  updatePreferences();
  updateSettings();

  resume(true);
}

4. Complex State Initialization

js
function initializeApp() {
  pause();

  loadUserData();
  loadAppSettings();
  loadCachedData();
  setupDefaultValues();
  restoreUserPreferences();

  resume(true);  // UI updates once with everything ready
}

❌ Not Needed

1. Simple Updates

js
// 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

js
// 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

js
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

js
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

js
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

js
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

js
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

js
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

js
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

js
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:

js
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:

js
pause();
user.name = 'John';
user.age = 30;
resume(true);  // Always resume!

Tip: Use try-finally to ensure resume is called:

js
pause();
try {
  user.name = 'John';
  user.age = 30;
} finally {
  resume(true);  // Always called
}

Pitfall #2: Mismatched Pause/Resume Counts

Wrong:

js
pause();
pause();  // Paused twice
user.name = 'John';
resume(true);  // Only resumed once - still paused!

user.age = 30;  // Won't trigger effects (still paused)

Correct:

js
pause();
pause();  // Paused twice
user.name = 'John';
resume(true);  // Resume once
resume(true);  // Resume again - now fully resumed

user.age = 30;  // Will trigger effects

Why? 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:

js
// Overusing pause() for simple scoped updates
function update() {
  pause();
  user.name = 'John';
  user.age = 30;
  resume(true);
}

Correct:

js
// 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:

js
pause();
user.name = 'John';
resume(false);  // Changes are still there!

console.log(user.name);  // "John" (not reverted)

Correct:

js
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:

js
// 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!