Skip to content

Sponsor

Sponsor

# Understanding Reactivity

Before learning DOM Helpers Reactive, let's first understand what reactivity is, why it is important, and how it makes building interactive applications easier.

What is Reactivity?

Reactivity is a programming paradigm where changes to data automatically trigger updates to anything that depends on that data, that means when data changes, the UI updates automatically.

You don't need to tell the app to refresh or update things manually. It just reacts to changes on its own.

Think of it like a spreadsheet:

  • Cell A1 contains the value 5
  • Cell B1 contains the formula =A1 * 2
  • When you change A1 to 10, B1 automatically updates to 20

You never have to manually recalculate B1—the spreadsheet "reacts" to changes in A1.

Reactivity brings this same automatic updating behavior to programming.

The Problem Reactivity Solves

Without Reactivity (The Traditional Way)

In traditional imperative programming, when data changes, nothing updates automatically. You must find and update every place that depends on that data yourself.

javascript
// State
let count = 0;

// UI elements
const counterDisplay = document.getElementById('counter');
const doubledDisplay = document.getElementById('doubled');
const messageDisplay = document.getElementById('message');

// Initial render
function updateUI() {
  counterDisplay.textContent = count;
  doubledDisplay.textContent = count * 2;
  messageDisplay.textContent = count > 10 ? 'High!' : 'Low';
}

// When something changes
function increment() {
  count++;
  updateUI(); // Must remember to call this!
}

// Initial display
updateUI();

Problems with this approach:

  1. Manual synchronization: You must remember to call updateUI() after every change
  2. Scattered updates: As your app grows, updates happen in many places
  3. Easy to forget: Miss one updateUI() call and your UI becomes out of sync
  4. No dependency tracking: You have to know which parts of UI depend on which data
  5. Performance: Hard to optimize—you might update things that didn't actually change

Real-World Example of the Problem

Imagine a shopping cart:

javascript
let cart = {
  items: [],
  total: 0,
  itemCount: 0,
  tax: 0,
  finalTotal: 0
};

function addItem(item) {
  cart.items.push(item);
  
  // Now you must manually update EVERYTHING:
  cart.itemCount = cart.items.length;
  cart.total = cart.items.reduce((sum, item) => sum + item.price, 0);
  cart.tax = cart.total * 0.08;
  cart.finalTotal = cart.total + cart.tax;
  
  // AND update the UI:
  updateCartDisplay();
  updateItemCountBadge();
  updateTotalDisplay();
  updateCheckoutButton();
  
  // Did you remember all of them? Easy to miss one!
}

This becomes unmaintainable as your application grows.

How Reactivity Solves This

With reactivity, you focus on what should happen when data changes, not when to make it happen:

javascript
// State (reactive)
const state = reactive({
  count: 0
});

// Derived values (automatically update)
const doubled = computed(() => state.count * 2);
const message = computed(() => state.count > 10 ? 'High!' : 'Low');

// UI updates (automatically run when dependencies change)
effect(() => {
  counterDisplay.textContent = state.count;
});

effect(() => {
  doubledDisplay.textContent = doubled.value;
});

effect(() => {
  messageDisplay.textContent = message.value;
});

// When something changes
function increment() {
  state.count++; // That's it! Everything updates automatically
}

What just happened:

  1. ✅ No manual updateUI() calls
  2. ✅ Automatically tracks which effects depend on which state
  3. ✅ Updates only what actually changed
  4. ✅ Impossible to forget an update
  5. ✅ Scales naturally as your app grows

The Shopping Cart—Reactive Version

javascript
const cart = reactive({
  items: []
});

// Derived values automatically update
const itemCount = computed(() => cart.items.length);
const total = computed(() => 
  cart.items.reduce((sum, item) => sum + item.price, 0)
);
const tax = computed(() => total.value * 0.08);
const finalTotal = computed(() => total.value + tax.value);

// UI updates automatically
effect(() => {
  cartDisplay.textContent = `Items: ${itemCount.value}`;
});

effect(() => {
  totalDisplay.textContent = `$${finalTotal.value.toFixed(2)}`;
});

// Now adding an item is simple:
function addItem(item) {
  cart.items.push(item); // Everything else updates automatically!
}

Much simpler. Add an item, and all derived values and UI updates happen automatically.

Reactive vs Non-Reactive: Side-by-Side

Scenario: User Profile Display

Non-Reactive (Traditional)

javascript
// State
let user = {
  firstName: 'John',
  lastName: 'Doe',
  age: 25
};

// Derived data (manual)
let fullName = user.firstName + ' ' + user.lastName;
let isAdult = user.age >= 18;

// Update function (manual)
function updateUserDisplay() {
  fullName = user.firstName + ' ' + user.lastName;
  isAdult = user.age >= 18;
  
  document.getElementById('name').textContent = fullName;
  document.getElementById('age').textContent = user.age;
  document.getElementById('status').textContent = 
    isAdult ? 'Adult' : 'Minor';
}

// Every change requires manual update
function updateFirstName(newName) {
  user.firstName = newName;
  updateUserDisplay(); // Must call!
}

function updateAge(newAge) {
  user.age = newAge;
  updateUserDisplay(); // Must call!
}

// Initial render
updateUserDisplay();

Problems:

  • 8 lines of repetitive update logic
  • Easy to forget updateUserDisplay()
  • fullName and isAdult can become stale
  • Hard to add new derived values

Reactive (Modern)

javascript
// State (reactive)
const user = reactive({
  firstName: 'John',
  lastName: 'Doe',
  age: 25
});

// Derived data (automatic)
const fullName = computed(() => 
  user.firstName + ' ' + user.lastName
);
const isAdult = computed(() => user.age >= 18);

// UI updates (automatic)
effect(() => {
  document.getElementById('name').textContent = fullName.value;
});

effect(() => {
  document.getElementById('age').textContent = user.age;
});

effect(() => {
  document.getElementById('status').textContent = 
    isAdult.value ? 'Adult' : 'Minor';
});

// Changes just work
function updateFirstName(newName) {
  user.firstName = newName; // Done!
}

function updateAge(newAge) {
  user.age = newAge; // Done!
}

Benefits:

  • No manual update calls
  • Derived values always up to date
  • Clear dependency relationships
  • Easy to add new computed values
  • Impossible to forget an update

How Reactivity Works (Simplified)

1. Dependency Tracking

When you access reactive state inside an effect or computed value, the system automatically tracks that dependency:

When reactive code runs, the system pays attention to which data is being read. Anything that is read becomes a dependency.

javascript
const state = reactive({ count: 0, name: 'Alice' });

effect(() => {
  console.log(state.count); // Tracks dependency on 'count' because it's being read
  // Does NOT track 'name' because we didn't access it
});

state.count++; // Effect runs ✓
state.name = 'Bob'; // Effect does NOT run ✗

How it works internally:

  1. When effect() starts running, it says "I'm tracking dependencies now"
  2. When you access state.count, the reactive system records: "This effect depends on count"
  3. When count changes, the system knows to re-run this effect
  4. When name changes, the system knows this effect doesn't care

2. Automatic Re-execution

When reactive state changes, any effects or computed values that depend on it automatically re-run:

javascript
const state = reactive({ x: 1, y: 2 });

const sum = computed(() => {
  return state.x + state.y; // Depends on both x and y
});

console.log(sum.value); // 3

state.x = 5; // sum automatically recalculates
console.log(sum.value); // 7

state.y = 10; // sum automatically recalculates  
console.log(sum.value); // 15

3. The Reactive Graph

Internally, the system builds a graph of dependencies:

State: { count: 5 }

   Computed: doubled (count * 2)

   Effect: Update UI with doubled value

When count changes:

  1. doubled is marked as "needs recalculation"
  2. Any effects depending on doubled are marked as "needs re-execution"
  3. The system runs updates in the correct order
  4. Everything stays synchronized automatically

Key Concepts in Reactivity

1. Reactive State

The source of truth. When this changes, everything that depends on it updates.

javascript
const state = reactive({
  temperature: 20
});

2. Computed Values

Derived data. Automatically recalculates when dependencies change. Derived data is data that is calculated from other data instead of being stored directly.

javascript
const fahrenheit = computed(() => 
  (state.temperature * 9/5) + 32
);

Simple Example (Real Life)

You have a price: 100 You calculate tax from the price You calculate total price from price + tax The total price is derived data because:

You didn't store it directly It is calculated from other values

Derived Data in Code:

javascript
const price = 100;
const total = price * 1.15;

price → original data total → derived data (it depends on price)

3. Effects

Side effects. Code that runs automatically when dependencies change (like updating the DOM).

javascript
effect(() => {
  thermometerDisplay.textContent = `${state.temperature}°C`;
});

4. Watchers

Observers. Run specific code when particular values change, with access to old and new values.

javascript
watch(
  () => state.temperature,
  (newTemp, oldTemp) => {
    if (newTemp > 30) {
      alert("It's hot!");
    }
  }
);

Common Patterns: Before and After Reactivity

Pattern 1: Form Input Sync

Before (Non-Reactive)

javascript
const input = document.getElementById('username');
const display = document.getElementById('display');

input.addEventListener('input', (e) => {
  const value = e.target.value;
  display.textContent = value; // Manual sync
  validateUsername(value);     // Manual call
  saveToLocalStorage(value);   // Manual call
  updateCharCount(value);      // Manual call
});

function validateUsername(value) {
  if (value.length < 3) {
    input.style.borderColor = 'red';
  } else {
    input.style.borderColor = 'green';
  }
}

// Save username to localStorage
function saveToLocalStorage(value) {
  localStorage.setItem('username', value);
}

// Update character count display
function updateCharCount(value) {
  charCount.textContent = value.length;
}

After (Reactive)

javascript
const state = reactive({ username: '' });

input.addEventListener('input', (e) => {
  state.username = e.target.value; // That's it!
});

// Everything else happens automatically:
effect(() => {
  display.textContent = state.username;
});

effect(() => {
  validateUsername(state.username);
});

effect(() => {
  localStorage.setItem('username', state.username);
});

effect(() => {
  charCount.textContent = state.username.length;
});

/*
With reactivity, you no longer need to manually call these functions:
  validateUsername(value);
  saveToLocalStorage(value);
  updateCharCount(value);

They run automatically when the data changes.
*/

Pattern 2: Conditional Display

Before (Non-Reactive)

javascript
let isLoggedIn = false;

function updateUI() {
  if (isLoggedIn) {
    loginButton.style.display = 'none';
    logoutButton.style.display = 'block';
    userPanel.style.display = 'block';
  } else {
    loginButton.style.display = 'block';
    logoutButton.style.display = 'none';
    userPanel.style.display = 'none';
  }
}

function login() {
  isLoggedIn = true;
  updateUI(); // Remember to call!
}

function logout() {
  isLoggedIn = false;
  updateUI(); // Remember to call!
}

After (Reactive)

javascript
const state = reactive({ isLoggedIn: false });

effect(() => {
  loginButton.style.display = state.isLoggedIn ? 'none' : 'block';
});

effect(() => {
  logoutButton.style.display = state.isLoggedIn ? 'block' : 'none';
});

effect(() => {
  userPanel.style.display = state.isLoggedIn ? 'block' : 'none';
});

function login() {
  state.isLoggedIn = true; // UI updates automatically
}

function logout() {
  state.isLoggedIn = false; // UI updates automatically
}

Pattern 3: Filtering a List

Before (Non-Reactive)

javascript
let items = ['Apple', 'Banana', 'Cherry'];
let filter = '';

function updateDisplay() {
  const filtered = items.filter(item =>
    item.toLowerCase().includes(filter.toLowerCase())
  );
  
  listContainer.innerHTML = '';
  filtered.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    listContainer.appendChild(li);
  });
}

searchInput.addEventListener('input', (e) => {
  filter = e.target.value;
  updateDisplay(); // Manual call
});

// Adding an item
function addItem(item) {
  items.push(item);
  updateDisplay(); // Manual call
}

updateDisplay(); // Initial render

After (Reactive)

javascript
const state = reactive({
  items: ['Apple', 'Banana', 'Cherry'],
  filter: ''
});

const filteredItems = computed(() =>
  state.items.filter(item =>
    item.toLowerCase().includes(state.filter.toLowerCase())
  )
);

effect(() => {
  listContainer.innerHTML = '';
  filteredItems.value.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    listContainer.appendChild(li);
  });
});

searchInput.addEventListener('input', (e) => {
  state.filter = e.target.value; // Automatically updates display
});

function addItem(item) {
  state.items.push(item); // Automatically updates display
}

Benefits of Reactivity

Reactivity changes how you think about building applications.
Instead of manually keeping everything in sync, you let the system do that work for you.

Below are the main benefits, explained in a simple and practical way.

1. Declarative Code

With reactive code, you describe what should happen, not when to make it happen.

You say:

  • “This text shows the current count”
  • “This style depends on this value”

You don’t write step-by-step instructions for every update.
The code becomes easier to read because it explains intent, not procedures.

2. Automatic Synchronization

Your data and your UI are always connected.

When data changes:

  • The UI updates automatically
  • No extra code is needed
  • No manual DOM updates

This removes the common problem of the UI getting out of sync with the data.

3. Reduced Bugs

In non-reactive code, it’s easy to forget an update:

  • You change the data
  • You forget to update one part of the UI
  • A bug appears

With reactivity, this problem disappears because:

  • Updates happen automatically
  • You can’t forget something that you don’t have to do manually

4. Better Performance

Reactive systems are smart about updates.

They:

  • Track what depends on what
  • Update only the parts that actually changed
  • Avoid unnecessary re-renders or DOM updates

This means better performance without extra effort from you.

5. Easier Refactoring

When your app grows, reactive code is easier to change.

Instead of:

  • Searching for every place where data is updated
  • Modifying many update calls

You simply:

  • Add a new effect
  • Or change existing logic in one place

This makes adding features safer and faster.

6. Clear Dependencies

Reactivity makes relationships clear.

You can easily see:

  • Which data a piece of code depends on
  • What updates when a value changes

This clarity helps both beginners and experienced developers understand the codebase quickly.

7. Scales Naturally

Reactivity works the same way at any size.

  • 10 reactive values → works smoothly
  • 1,000 reactive values → still works the same way

You don’t need to change how you write code as your app grows.
The same patterns continue to work naturally.

Final Takeaway

Reactivity lets you:

  • Write less code
  • Avoid common bugs
  • Focus on logic instead of updates

You change the data, and the rest just works.

When Reactivity Shines

Reactivity is especially powerful for:

  • Real-time interfaces: Dashboards, live data, chat apps
  • Forms: Validation, character counts, conditional fields
  • Complex state: Shopping carts, multi-step wizards
  • Derived data: Calculations, filters, transformations
  • Conditional UI: Show/hide based on state
  • Animations: Respond to state changes

The Mental Shift

Moving from imperative to reactive programming requires a mindset shift:

Imperative Thinking (Before)

"When the user clicks this button, I need to update these three things, recalculate this value, and refresh the UI."

Reactive Thinking (After)

"This state represents the count. The UI should always show the current count. When the count changes, the UI updates automatically."

You stop thinking about HOW to update things and start thinking about WHAT the relationships are.

Ready for DOM Helpers Reactive?

Now that you understand:

  • What reactivity is
  • What problems it solves
  • How it works under the hood
  • The benefits it provides

You're ready to see how DOM Helpers Reactive implements these concepts in a way that:

  • Works standalone or with DOM Helpers Core
  • Stays close to vanilla JavaScript
  • Scales from simple to complex
  • Gives you full control when needed

Let's dive into DOM Helpers Reactive and see these principles in action.