Skip to content

Common Anti-Patterns

When to Use

Use when reviewing code for mistakes and understanding what NOT to do.

Decision

Avoid these patterns that create bugs, performance issues, or maintainability problems. Each anti-pattern has a WHY explanation - understanding the consequences helps prevent them.

Pattern

Anti-Pattern 1: No context parameter

// WRONG
Drupal.behaviors.bad = {
  attach() {  // Missing context!
    $('.element').each(function() {
      // Scans entire document every time
    });
  }
};

// CORRECT
Drupal.behaviors.good = {
  attach(context) {
    once('good', '.element', context).forEach(function(element) {
      // Scans only new/updated content
    });
  }
};

Why: Without context, code scans entire DOM on every AJAX request. Severe performance penalty, especially on large pages.


Anti-Pattern 2: Missing once()

// WRONG
$('.button', context).on('click', handler);
// Binds handler multiple times on AJAX updates

// CORRECT
once('click-handler', '.button', context).forEach(function(button) {
  button.addEventListener('click', handler);
  // Binds exactly once per element
});

Why: Without once(), event handlers bind multiple times on AJAX updates, causing memory leaks and duplicate execution.


Anti-Pattern 3: jQuery for simple operations

// WRONG - Adds 30KB jQuery dependency
$(element).addClass('active');
$(element).on('click', handler);

// CORRECT - Native JavaScript
element.classList.add('active');
element.addEventListener('click', handler);

Why: jQuery adds significant weight for operations vanilla JS handles. Drupal is phasing out jQuery.


Anti-Pattern 4: Missing detach()

// WRONG - No cleanup
Drupal.behaviors.leaky = {
  attach(context) {
    setInterval(updateStatus, 1000); // Interval never cleared
    window.addEventListener('resize', handler); // Never removed
  }
  // No detach() method
};

// CORRECT - Cleanup in detach
Drupal.behaviors.clean = {
  attach(context) {
    once('clean', 'html').forEach(function() {
      this.intervalId = setInterval(updateStatus, 1000);
    });
  },
  detach(context, settings, trigger) {
    if (trigger === 'unload') {
      clearInterval(this.intervalId);
    }
  }
};

Why: Intervals and global event listeners persist after content removal, causing memory leaks.


Anti-Pattern 5: Inline JavaScript

{# WRONG #}
<button onclick="doSomething()">Click</button>
<script>
  function doSomething() { }
</script>

{# CORRECT #}
<button class="action-button">Click</button>
{# JavaScript in behavior with library attachment #}

Why: Inline JS bypasses asset system, breaks CSP, prevents aggregation, violates separation of concerns.


Anti-Pattern 6: Global scope pollution

// WRONG - Pollutes global scope
function myFunction() { }
var myVariable = 'value';

// CORRECT - Wrapped in IIFE
(function (Drupal) {
  'use strict';

  function myFunction() { }
  const myVariable = 'value';

  // Only expose what's necessary
  Drupal.behaviors.myBehavior = { };
})(Drupal);

Why: Global variables conflict with other scripts, create unpredictable bugs.


Anti-Pattern 7: $(document).ready()

// WRONG - Only runs once, breaks with AJAX
$(document).ready(function() {
  $('.element').initializeWidget();
});

// CORRECT - Runs on page load AND AJAX updates
Drupal.behaviors.widget = {
  attach(context) {
    once('widget', '.element', context).forEach(function(element) {
      initializeWidget(element);
    });
  }
};

Why: $(document).ready() only executes on initial page load. AJAX-loaded content never initializes.


Anti-Pattern 8: innerHTML with user data

// WRONG - XSS vulnerability
element.innerHTML = '<div>' + userInput + '</div>';

// CORRECT - Safe alternatives
element.textContent = userInput;  // Text only
element.innerHTML = Drupal.checkPlain(userInput); // Escaped HTML

Why: Direct innerHTML with user input allows script injection and XSS attacks.

Common Mistakes

  • Wrong: Not understanding WHY anti-patterns are bad → Right: Learn the consequences
  • Why: Prevents repeating mistakes in different forms
  • Wrong: Copy-pasting old Drupal 7 patterns → Right: Use modern Drupal patterns
  • Why: Many D7 patterns are anti-patterns in modern Drupal
  • Wrong: Thinking "it works" means "it's correct" → Right: Test with AJAX, caching, scale
  • Why: Anti-patterns often work initially but fail in production

See Also