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:
Set loading state → Show spinner
Fetch data (await)
Success → Update state with data
Error → Catch and set error state
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
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 + modifier class="button button--large"
CSS Patterns
Custom Properties (Variables)
:root {
/* Colors */
--color-primary : #FFE600 ;
--color-secondary : #3483FA ;
/* Spacing */
--spacing-sm : 0.5 rem ;
--spacing-md : 1 rem ;
--spacing-lg : 1.5 rem ;
/* Transitions */
--transition-fast : 150 ms 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 ( 250 px , 1 fr ));
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 ( -8 px );
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' ;
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 : 2 px solid var ( --color-secondary );
outline-offset : 2 px ;
}
Validation Patterns
< 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