DEV Community

Cover image for Heard of Reactivity in Vanilla JavaScript?
Muyiwa Johnson
Muyiwa Johnson

Posted on

Heard of Reactivity in Vanilla JavaScript?

Reactivity in Vanilla JavaScript

Many developers shy away from building complex web applications with vanilla JavaScript, often citing a lack of built-in structure and features. While frameworks like React, Vue, and Angular offer powerful abstractions, it's easy to forget that the underlying principles can be implemented directly in JavaScript. One of the most crucial of these principles is reactivity: the ability for the UI to automatically update in response to data changes.

This article explores how to create a basic reactivity system using vanilla JavaScript, providing a deeper understanding of how modern UI frameworks operate under the hood.

The Challenge: Why Vanilla JavaScript Isn't Reactive by Default

Vanilla JavaScript lacks a built-in mechanism for automatically tracking data dependencies and triggering UI updates. This means that when a value changes, you must manually update the DOM to reflect that change. This approach can become cumbersome and error-prone, especially in larger applications. Consider this example:

const state = { count: 0 };
const counterEl = document.getElementById('counter');

// Problem: The UI doesn't update when state changes!
state.count++; // Nothing happens visually

// Manual update required
counterEl.textContent = state.count;
Enter fullscreen mode Exit fullscreen mode

In this scenario, incrementing state.count doesn't automatically update the counterEl element. We need to explicitly set the textContent property to reflect the new value. The core issue lies in this disconnect:

state.count++; // Changes data but doesn't update UI
Enter fullscreen mode Exit fullscreen mode

This lack of connection between data modifications and UI rendering is the fundamental problem that reactivity systems solve.

The Solution: Implementing Reactivity with Proxies

Fortunately, JavaScript provides a powerful tool for implementing reactivity: Proxies.

Understanding Proxies

Proxies allow you to intercept and redefine fundamental operations on an object, such as property access, assignment, and deletion. This enables you to observe and react to changes in an object's state. Proxies rely on traps, which are methods that intercept specific operations. For example, the set trap is triggered whenever a property is assigned a new value.

Consider this example:

const country = new Proxy(
  {},
  {
    set(target, property, value) {
      if (property === 'capital' && value !== 'Abuja') {
        throw new Error('Capital must be Abuja!');
      }
      target[property] = value;
      return true;
    },
  }
);

country.capital = 'Lagos'; // Error: Capital must be Abuja!

country.capital = 'Abuja'; // No error
console.log(country.capital); // Abuja
Enter fullscreen mode Exit fullscreen mode

In this example, the set trap intercepts assignments to the capital property. If the assigned value is not "Abuja", an error is thrown, enforcing a constraint on the object's state.

Building a Simple Reactivity System

We can leverage Proxies to create a basic reactivity system that automatically updates the UI whenever the state changes:

function createReactiveState(initialState, renderCallback) {
  return new Proxy(initialState, {
    set(target, property, value) {
      // Update the property
      target[property] = value;

      // Call the render function whenever state changes
      renderCallback();

      return true;
    },
  });
}

// Example usage
const appState = createReactiveState({ count: 0 }, () => {
  document.getElementById('counter').textContent = appState.count;
});

document.getElementById('increment').addEventListener('click', () => {
  appState.count++;
});
Enter fullscreen mode Exit fullscreen mode

In this example, the createReactiveState function takes an initial state object and a renderCallback function. It returns a Proxy that intercepts set operations. Whenever a property is assigned a new value, the set trap updates the property and then invokes the renderCallback function, allowing you to update the UI.

Using this system, we can achieve automatic UI updates with minimal code:

// Setup (one time)
const state = createReactiveState(
  { count: 0 },
  () => (document.getElementById('counter').textContent = state.count)
);

state.count++; // UI updates AUTOMATICALLY
state.count = 100; // this also works (UI updates AUTOMATICALLY)
Enter fullscreen mode Exit fullscreen mode

Conclusion

This simple example demonstrates the core concept of reactivity. By leveraging Proxies, you can build systems that automatically track data dependencies and trigger UI updates, reducing boilerplate code and improving the maintainability of your applications.

While this is a basic implementation, it showcases the fundamental principles behind more complex reactivity systems used in modern UI frameworks and libraries. Understanding these principles empowers you to build more efficient and maintainable web applications, regardless of the tools you choose.

Heroku

Built for developers, by developers.

Whether you're building a simple prototype or a business-critical product, Heroku's fully-managed platform gives you the simplest path to delivering apps quickly — using the tools and languages you already love!

Learn More

Top comments (0)

ACI image

ACI.dev: Fully Open-source AI Agent Tool-Use Infra (Composio Alternative)

100% open-source tool-use platform (backend, dev portal, integration library, SDK/MCP) that connects your AI agents to 600+ tools with multi-tenant auth, granular permissions, and access through direct function calling or a unified MCP server.

Check out our GitHub!

Join the Runner H "AI Agent Prompting" Challenge: $10,000 in Prizes for 20 Winners!

Runner H is the AI agent you can delegate all your boring and repetitive tasks to - an autonomous agent that can use any tools you give it and complete full tasks from a single prompt.

Check out the challenge

DEV is bringing live events to the community. Dismiss if you're not interested. ❤️