Skip to main content
This reference provides common code patterns used throughout the ML Store project, organized by category.

State Management Patterns

Centralized State Object

interface AppState {
  status: LoadingState;
  products: Product[];
  error: string | null;
}

let appState: AppState = {
  status: LoadingState.Idle,
  products: [],
  error: null
};
Benefits:
  • Single source of truth
  • Easy to debug (check one object)
  • Simple to save/restore (localStorage)
  • Predictable state changes
Usage:
// Update state
appState.status = LoadingState.Loading;
appState.products = data;

// Check state
if (appState.status === LoadingState.Success) {
  renderProducts();
}

Enum for Status

enum LoadingState {
  Idle = "IDLE",
  Loading = "LOADING",
  Success = "SUCCESS",
  Error = "ERROR"
}

// Use in switch statement
switch (appState.status) {
  case LoadingState.Loading:
    showSpinner();
    break;
  case LoadingState.Success:
    renderProducts();
    break;
  case LoadingState.Error:
    showError();
    break;
}
Using enums prevents typos and provides autocomplete. Better than magic strings!

DOM Manipulation Patterns

Generic Element Selector

function getElement<T extends HTMLElement>(selector: string): T {
  const element = document.querySelector<T>(selector);
  if (!element) {
    throw new Error(`Element not found: ${selector}`);
  }
  return element;
}

// Usage with type safety
const button = getElement<HTMLButtonElement>("#load-btn");
const input = getElement<HTMLInputElement>("#search");
const grid = getElement<HTMLDivElement>("#products-grid");
Without generics:
const button = document.querySelector("#load-btn");
button.disabled = true;  // ✗ Error: Property 'disabled' may not exist
With generics:
const button = getElement<HTMLButtonElement>("#load-btn");
button.disabled = true;  // ✓ TypeScript knows it's a button

Event Delegation

function setupAddToCartButtons(): void {
  const grid = getElement<HTMLDivElement>("#products-grid");
  
  grid.addEventListener("click", (event: MouseEvent) => {
    const target = event.target as HTMLElement;
    const button = target.closest("[data-action='add-to-cart']");
    
    if (button) {
      const card = button.closest("[data-product-id]");
      if (card) {
        const productId = parseInt(card.getAttribute("data-product-id")!);
        addToCart(productId);
      }
    }
  });
}
One listener for all buttons - works with dynamically added elements
Uses closest() to find parent elements
Reads data from data-* attributes

API Integration Patterns

Fetch with Error Handling

async function fetchProducts(limit: number = 20): Promise<Product[]> {
  const url = `${API_URL}?limit=${limit}`;
  console.log(`Fetching: ${url}`);
  
  const response = await fetch(url);
  
  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }
  
  const data = await response.json() as Product[];
  console.log(`Products received: ${data.length}`);
  
  return data;
}

Loading Pattern with Try/Catch

async function loadProducts(): Promise<void> {
  try {
    // Set loading state
    appState.status = LoadingState.Loading;
    appState.error = null;
    updateUI();
    
    // Fetch data
    const data = await fetchProducts(20);
    
    // Success
    appState.status = LoadingState.Success;
    appState.products = data;
    updateUI();
    
  } catch (error) {
    // Error handling
    appState.status = LoadingState.Error;
    appState.error = error instanceof Error 
      ? error.message 
      : "Unknown error";
    updateUI();
    
    console.error("Error loading products:", error);
  }
}
Flow:
  1. Set loading state → Show spinner
  2. Fetch data (await)
  3. Success → Update state with data
  4. Error → Catch and set error state
  5. Always call updateUI() to reflect changes
Benefits:
  • User sees loading feedback
  • Errors are handled gracefully
  • UI always reflects current state

Data Structure Patterns

Map for Key-Value Storage

const cart: Map<number, number> = new Map();
// Map<productId, quantity>

function addToCart(productId: number): void {
  const currentQty = cart.get(productId) || 0;
  cart.set(productId, currentQty + 1);
  updateCartBadge();
}

function removeFromCart(productId: number): void {
  cart.delete(productId);
  updateCartBadge();
}

function getCartTotal(): number {
  let total = 0;
  for (const qty of cart.values()) {
    total += qty;
  }
  return total;
}
Use Map instead of objects when:
  • Keys are numbers or need to be non-strings
  • You need .size, .has(), .delete() methods
  • You frequently add/remove entries

Array Transformation with Map/Join

function renderProducts(): void {
  const grid = getElement<HTMLDivElement>("#products-grid");
  
  grid.innerHTML = appState.products
    .map(product => createProductCardHTML(product))
    .join('');
  
  setupAddToCartButtons();
}
Pattern: Array → map to HTML strings → join → set innerHTML

Template Patterns

Template Literals for HTML

function createProductCardHTML(product: Product): string {
  const formattedPrice = product.price.toLocaleString('es-MX', {
    style: 'currency',
    currency: 'MXN'
  });
  
  const imageUrl = product.images[0] || 'https://placehold.co/400x300';
  const cleanImageUrl = imageUrl.replace(/["\[\]]/g, '');
  
  return `
    <article class="product-card" data-product-id="${product.id}">
      <figure class="product-card__figure">
        <img 
          src="${cleanImageUrl}" 
          alt="${product.title}"
          class="product-card__image"
          loading="lazy"
          onerror="this.src='https://placehold.co/400x300?text=Error'"
        />
      </figure>
      <div class="product-card__content">
        <span class="product-card__category">${product.category.name}</span>
        <h3 class="product-card__title">${product.title}</h3>
        <p class="product-card__price">
          ${formattedPrice}
          <span class="product-card__shipping">Envío gratis</span>
        </p>
        <button type="button" class="product-card__btn" data-action="add-to-cart">
          Agregar al carrito
        </button>
      </div>
    </article>
  `;
}
Be careful with XSS when using template literals with user data. Always escape or sanitize if needed.

BEM Naming Pattern

Block Element Modifier Structure

<!-- Block -->
<header class="header">
  
  <!-- Element -->
  <div class="header__container">
    
    <!-- Element -->
    <div class="header__logo">
      <a href="/" class="header__logo-link">
        
        <!-- Element -->
        <span class="header__logo-text">ML</span>
        <span class="header__logo-subtitle">Store</span>
      </a>
    </div>
    
    <!-- Element with Modifier -->
    <a href="#" class="header__nav-link header__nav-link--cart">
      Carrito
    </a>
    
  </div>
</header>
Naming Rules:
  • Block: .block
  • Element: .block__element
  • Modifier: .block__element--modifier

Block

Independent component.header, .product-card, .footer

Element

Part of block (__).header__logo, .product-card__image

Modifier

Variation (—).button--primary, .card--featured

Combined

Base + modifierclass="button button--large"

CSS Patterns

Custom Properties (Variables)

:root {
  /* Colors */
  --color-primary: #FFE600;
  --color-secondary: #3483FA;
  
  /* Spacing */
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  
  /* Transitions */
  --transition-fast: 150ms ease;
}

/* Usage */
.button {
  background-color: var(--color-primary);
  padding: var(--spacing-md);
  transition: all var(--transition-fast);
}

Flexbox Centering

/* Horizontal and vertical centering */
.container {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Horizontal spacing */
.nav {
  display: flex;
  gap: var(--spacing-md);
}

/* Flex item that grows */
.search {
  flex: 1;
  min-width: 0;
}

Responsive Grid

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: var(--spacing-lg);
}
Automatically responsive without media queries!

Smooth Transitions

.card {
  transition: transform var(--transition-base),
              box-shadow var(--transition-base);
}

.card:hover {
  transform: translateY(-8px);
  box-shadow: var(--shadow-lg);
}

Error Handling Patterns

Graceful Degradation

function showNotification(message: string, type: 'success' | 'error'): void {
  const notification = document.createElement('div');
  notification.className = `notification notification--${type}`;
  notification.textContent = message;
  
  document.body.appendChild(notification);
  
  setTimeout(() => {
    notification.remove();
  }, 3000);
}

// Usage
try {
  await loadProducts();
  showNotification('Products loaded!', 'success');
} catch (error) {
  showNotification('Failed to load products', 'error');
}

Fallback Values

// Image fallback
const imageUrl = product.images[0] || 'https://placehold.co/400x300';

// Default parameter
function fetchProducts(limit: number = 20): Promise<Product[]> {
  // ...
}

// Nullish coalescing
const quantity = cart.get(productId) || 0;

// Optional chaining
const categoryName = product?.category?.name ?? 'Unknown';

Performance Patterns

Lazy Loading Images

<img 
  src="product.jpg"
  alt="Product"
  loading="lazy"
/>

Debouncing (Concept)

function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: number | undefined;
  
  return function(...args: Parameters<T>) {
    clearTimeout(timeout);
    timeout = window.setTimeout(() => func(...args), wait);
  };
}

// Usage
const debouncedSearch = debounce((query: string) => {
  searchProducts(query);
}, 300);

searchInput.addEventListener('input', (e) => {
  debouncedSearch((e.target as HTMLInputElement).value);
});

Accessibility Patterns

ARIA Labels

<button 
  type="submit" 
  class="search-button" 
  aria-label="Search products"
>
  <svg><!-- Icon --></svg>
</button>

Keyboard Navigation

element.addEventListener('keydown', (event: KeyboardEvent) => {
  if (event.key === 'Enter' || event.key === ' ') {
    event.preventDefault();
    handleAction();
  }
});

Focus Management

button:focus-visible,
a:focus-visible {
  outline: 2px solid var(--color-secondary);
  outline-offset: 2px;
}

Validation Patterns

Form Validation

<input 
  type="email"
  name="email"
  required
  pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
/>

Type Guards

function isProduct(obj: any): obj is Product {
  return (
    typeof obj === 'object' &&
    typeof obj.id === 'number' &&
    typeof obj.title === 'string' &&
    typeof obj.price === 'number'
  );
}

// Usage
if (isProduct(data)) {
  // TypeScript knows data is Product here
  console.log(data.title);
}

Testing Patterns

Manual Testing Helpers

// Log state changes
function updateState(newState: Partial<AppState>): void {
  console.log('State before:', { ...appState });
  Object.assign(appState, newState);
  console.log('State after:', { ...appState });
  updateUI();
}

// Debug helper
function debugCart(): void {
  console.table(Array.from(cart.entries()).map(([id, qty]) => ({
    productId: id,
    quantity: qty
  })));
}

Code Organization

Module Pattern

// constants.ts
export const API_URL = "https://api.example.com";
export const MAX_PRODUCTS = 50;

// types.ts
export interface Product { /* ... */ }
export enum LoadingState { /* ... */ }

// utils.ts
export function getElement<T>(...) { /* ... */ }

// main.ts
import { API_URL } from './constants';
import { Product, LoadingState } from './types';
import { getElement } from './utils';

Best Practices Summary

State Management

  • Use centralized state object
  • Use enums for status
  • Update UI on state change

DOM Manipulation

  • Use event delegation
  • Type your elements with generics
  • Use data attributes for metadata

API Calls

  • Always handle errors
  • Show loading states
  • Validate responses

Code Style

  • Use BEM for CSS classes
  • Use TypeScript types
  • Keep functions small and focused

Next Steps

Project Tutorial

See these patterns in action

TypeScript Types

TypeScript reference

CSS Properties

CSS reference guide

Build docs developers (and LLMs) love