-
Notifications
You must be signed in to change notification settings - Fork 88
TypeScriptConventions
Allan Michay edited this page Dec 10, 2025
·
2 revisions
Prefer discriminated unions to make prop relationships explicit and prevent optional prop ambiguity:
// ❌ Bad - Optional props create ambiguity
type Props = {
authenticated: boolean;
level?: "basic" | "admin";
};
// ✅ Good - Discriminated union makes the relationship clear
type Props =
| { authenticated: true; level: "basic" | "admin" }
| { authenticated: false };This pattern:
- Makes it clear that
levelis only relevant whenauthenticatedistrue - Provides better TypeScript inference and autocompletion
- Prevents invalid prop combinations at compile time
Always declare explicit return types for functions:
// ❌ Bad - Implicit return type
const calculateTotal = (items: Item[]) => {
return items.reduce((sum, item) => sum + item.price, 0);
};
// ✅ Good - Explicit return type
const calculateTotal = (items: Item[]): number => {
return items.reduce((sum, item) => sum + item.price, 0);
};Benefits:
- Catches errors early when implementation doesn't match expected type
- Improves code readability and documentation
- Makes refactoring safer
Avoid ternary expressions deeper than 2 levels:
// ❌ Bad - Nested ternary
const role = isAdmin ? (isManager ? "Manager" : "Admin") : "User";
// ✅ Good - Extract to function with early returns
function getRole(): string {
if (!isAdmin) return "User";
if (isManager) return "Manager";
return "Admin";
}
const role = getRole();Use early returns to reduce nesting and improve readability:
// ❌ Bad - Deeply nested
function getStatus(user: User): string {
let status;
if (user.isActive) {
if (user.isAdmin) {
status = "Admin";
} else {
status = "Active";
}
} else {
status = "Inactive";
}
return status;
}
// ✅ Good - Early returns
function getStatus(user: User): string {
if (!user.isActive) return "Inactive";
if (user.isAdmin) return "Admin";
return "Active";
}Extract complex logic above the JSX return:
// ❌ Bad - Logic embedded in JSX
return <div>{score > 80 ? "High" : score > 50 ? "Medium" : "Low"}</div>;
// ✅ Good - Logic extracted
let label: string;
if (score > 80) {
label = "High";
} else if (score > 50) {
label = "Medium";
} else {
label = "Low";
}
return <div>{label}</div>;Use the ~/* path alias which maps to src/*:
// ✅ Good
import { Button } from "~/components/designSystem";
import { useCustomer } from "~/hooks/useCustomer";
// ❌ Avoid relative paths for deep imports
import { Button } from "../../../components/designSystem";Use type imports when importing only types:
import type { Customer, Invoice } from "~/generated/graphql";
import { useGetCustomerQuery } from "~/generated/graphql";| Type | Convention | Example |
|---|---|---|
| Variables | camelCase | customerName |
| Functions | camelCase | getCustomer() |
| Components | PascalCase | CustomerCard |
| Types/Interfaces | PascalCase | CustomerProps |
| Constants | SCREAMING_SNAKE_CASE | MAX_RETRY_COUNT |
| Files (components) | PascalCase | CustomerCard.tsx |
| Files (utilities) | camelCase | formatCurrency.ts |