JavaScript and TypeScript
These guidelines define JavaScript and TypeScript coding standards for SlideSpeak.
Prefer Types Over Interfaces
Section titled “Prefer Types Over Interfaces”Always use type declarations instead of interface for TypeScript definitions. Types are more flexible and consistent with our codebase patterns.
❌ Bad: Using interface
export interface UserProps { name: string; email: string; age?: number;}✅ Good: Using type
export type UserProps = { name: string; email: string; age?: number;};When extending HTML attributes, use intersection types:
export type CardProps = { title: string; description?: string;} & HTMLAttributes<HTMLDivElement>;Use Const Arrow Functions
Section titled “Use Const Arrow Functions”Always use const syntax with arrow functions instead of function declarations:
❌ Bad: Function declaration
function calculateTotal(items: Item[]) { return items.reduce((sum, item) => sum + item.price, 0);}✅ Good: Const arrow function
const calculateTotal = (items: Item[]) => { return items.reduce((sum, item) => sum + item.price, 0);};Prefer String Literals Over Enums
Section titled “Prefer String Literals Over Enums”Avoid TypeScript enum. Use string literal types instead. String literals must be uppercase.
❌ Bad: Using enum
enum UserRole { ADMIN = 'ADMIN', USER = 'USER', GUEST = 'GUEST',}✅ Good: Using string literal types
export type UserRole = 'ADMIN' | 'USER' | 'GUEST';export type OrderStatus = 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'CANCELLED';export type NotificationType = 'SUCCESS' | 'ERROR' | 'WARNING' | 'INFO';When you need a const object for runtime values, use as const:
const STATUS_MESSAGES = { PENDING: 'Your order is pending', PROCESSING: 'Your order is being processed', COMPLETED: 'Your order has been completed',} as const;
type OrderStatus = keyof typeof STATUS_MESSAGES;Functional Patterns
Section titled “Functional Patterns”Use functional programming patterns with array methods instead of imperative loops:
❌ Bad: Imperative loop
const activeUsers = [];for (let i = 0; i < users.length; i++) { if (users[i].isActive) { activeUsers.push(users[i]); }}✅ Good: Functional approach
const activeUsers = users.filter(user => user.isActive);const userNames = activeUsers.map(user => user.name);const sortedUsers = [...activeUsers].sort((a, b) => a.name.localeCompare(b.name));Use functional composition for data transformations:
const processedData = data .filter(item => !item.deleted) .map(item => transformItem(item)) .sort((a, b) => a.priority - b.priority);Prefer array methods like .map(), .filter(), .find(), .reduce(), and .toSorted() over imperative loops.
Avoid Mutating Data
Section titled “Avoid Mutating Data”Prefer creating new objects and arrays instead of mutating existing ones. Mutating data leads to unexpected bugs because multiple parts of your code might reference the same object. When you change that object in one place, those changes appear everywhere, making it hard to track where modifications happened and why.
❌ Bad: Mutating existing objects
const user = { name: 'John', age: 30 };user.age = 31;items.push(newItem);config.settings.theme = 'dark';✅ Good: Creating new objects
const updatedUser = { ...user, age: 31 };const newItems = [...items, newItem];const updatedConfig = { ...config, settings: { ...config.settings, theme: 'dark' } };This is especially important in React. React uses reference equality to determine if state or props have changed. If you mutate an object directly, React won’t detect the change because the object reference stays the same. This means your component won’t re-render when it should, leading to stale UI. React expects you to return new objects from state setters so it can properly track changes and trigger re-renders.
When working with arrays, use methods that return new arrays like .map(), .filter(), .toSorted(), and spread syntax instead of mutating methods like .push(), .pop(), .sort(), or direct index assignment.