Skip to content

JavaScript and TypeScript

These guidelines define JavaScript and TypeScript coding standards for SlideSpeak.

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>;

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);
};

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;

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.

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.