by InlinexDev

Vanilla JavaScript vs React: When Simple Wins Over Framework Complexity

When to choose vanilla JavaScript over React for web applications, with real examples from production B2B portals and embedded widgets.

JavaScriptReactvanilla JSfrontendweb development

The Framework Default

React is the default choice for web frontends. But reaching for a framework when plain JavaScript suffices adds complexity, bundle size, and maintenance burden that many projects don't need.

After building production applications with both approaches, here's when vanilla JS is the better choice.

Case Study: B2B Wholesale Portal

The FlyingEagle B2B portal serves a few dozen dealers. The entire frontend is vanilla JavaScript. Here's why that was the right call.

The Numbers

| Metric | React Version (estimated) | Vanilla JS (actual) | |--------|-------------------------|--------------------| | Bundle size | ~150KB (gzipped) | ~12KB (gzipped) | | First load | ~1.5s | ~0.3s | | Dependencies | 50+ npm packages | 0 npm packages | | Build step | Required (Webpack/Vite) | None | | Time to initial render | After JS parse + hydrate | Immediate (HTML) |

How We Structure Vanilla JS

Vanilla JS doesn't mean spaghetti code. With modern browser APIs, you can build well-structured applications:

// Simple component pattern
class ProductCatalog {
  constructor(container, options) {
    this.container = container;
    this.products = [];
    this.filters = { category: '', search: '' };
    this.init();
  }

  async init() {
    this.products = await this.fetchProducts();
    this.render();
    this.bindEvents();
  }

  async fetchProducts() {
    const response = await fetch('/api/products');
    return response.json();
  }

  render() {
    const filtered = this.getFilteredProducts();
    this.container.innerHTML = `
      <div class="catalog-header">
        <input type="search" class="search-input" 
               placeholder="Search products..." 
               value="${this.filters.search}">
        <select class="category-filter">
          <option value="">All Categories</option>
          ${this.getCategories().map(c => 
            `<option value="${c}" ${c === this.filters.category ? 'selected' : ''}>${c}</option>`
          ).join('')}
        </select>
      </div>
      <div class="product-grid">
        ${filtered.map(p => this.renderProduct(p)).join('')}
      </div>
    `;
  }

  renderProduct(product) {
    return `
      <div class="product-card" data-sku="${product.sku}">
        <img src="${product.image}" alt="${product.name}" loading="lazy">
        <h3>${product.name}</h3>
        <p class="sku">${product.sku}</p>
        <p class="price">$${product.price.toFixed(2)}</p>
        <div class="qty-control">
          <button class="qty-btn" data-action="decrease">-</button>
          <input type="number" class="qty-input" value="0" min="0">
          <button class="qty-btn" data-action="increase">+</button>
        </div>
      </div>
    `;
  }

  bindEvents() {
    this.container.addEventListener('input', (e) => {
      if (e.target.classList.contains('search-input')) {
        this.filters.search = e.target.value;
        this.render();
      }
    });

    this.container.addEventListener('change', (e) => {
      if (e.target.classList.contains('category-filter')) {
        this.filters.category = e.target.value;
        this.render();
      }
    });
  }

  getFilteredProducts() {
    return this.products.filter(p => {
      if (this.filters.category && p.category !== this.filters.category) return false;
      if (this.filters.search && !p.name.toLowerCase().includes(this.filters.search.toLowerCase())) return false;
      return true;
    });
  }
}

// Usage
new ProductCatalog(document.getElementById('catalog'));

When Vanilla JS Wins

1. Embedded Widgets

The Inlinex booking widget is an iframe with vanilla JS. Adding React to a widget that needs to be tiny and fast makes no sense. The entire widget is under 15KB.

2. Server-Rendered Pages with Light Interactivity

If your server (Flask, Express) renders the HTML and you just need some interactivity (form validation, modals, filters), vanilla JS is all you need.

3. Small User Base

The B2B portal has 30 users. React's developer experience benefits don't outweigh the complexity when the user base is small and the feature set is stable.

4. No Build Step Required

Vanilla JS works directly in the browser. No Webpack, no Vite, no Babel. This means:

  • Faster development iteration
  • Simpler deployment (just serve static files)
  • No build tool configuration to maintain
  • Easier debugging (source code matches running code)

When React Wins

1. Complex State Management

If your app has deeply nested state that changes frequently (real-time dashboards, collaborative editors), React's state management and virtual DOM reconciliation save you from manual DOM manipulation bugs.

2. Large Teams

React's component model and ecosystem (TypeScript, testing libraries, linting) help large teams maintain consistency.

3. Shopify Apps

Shopify's app framework is built on React (Remix). Fighting the framework would create more problems than it solves. LogoBadge uses React because that's what Shopify expects.

4. Frequent UI Updates

Apps where many DOM elements update frequently (data tables with live editing, drag-and-drop interfaces) benefit from React's efficient reconciliation.

Modern Vanilla JS APIs You Should Know

// Template literals for HTML
const html = `<div class="card">${data.title}</div>`;

// Event delegation (no need to bind individual elements)
container.addEventListener('click', (e) => {
  const button = e.target.closest('[data-action]');
  if (button) handleAction(button.dataset.action);
});

// Fetch API (no axios needed)
const data = await fetch('/api/data').then(r => r.json());

// URLSearchParams for query strings
const params = new URLSearchParams(window.location.search);

// IntersectionObserver for lazy loading
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) loadImage(entry.target);
  });
});

// CustomEvent for component communication
document.dispatchEvent(new CustomEvent('cart-updated', { detail: { count: 5 } }));

The Decision Framework

Ask these questions:

  1. How many interactive elements? Under 10 dynamic components? Vanilla JS.
  2. How many developers? Solo or small team? Vanilla JS is fine.
  3. Does the platform dictate it? Shopify apps need React. Go with the platform.
  4. How complex is the state? Simple forms and lists? Vanilla JS. Complex nested state? React.
  5. What's the bundle budget? If the entire app should be under 50KB, skip the framework.

Conclusion

React is excellent — when you need it. But for embedded widgets, server-rendered pages with light interactivity, and small B2B portals, vanilla JavaScript delivers better performance, simpler deployment, and lower maintenance. Don't add framework complexity because it's the default. Choose the tool that fits the problem.

Related Project

Flying Eagle B2B Wholesale Portal

A B2B wholesale ordering portal for Flying Eagle inline skates, enabling distributors to browse catalogs, manage inventory, and place bulk orders.