TypeScript Best Practices for 2024

TypeScript Best Practices for 2024

TypeScript has become an essential tool in modern web development. Let's explore the best practices that will help you write better TypeScript code in 2024.

Type Safety First

1. Strict Mode

Always enable strict mode in your tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
  }
}

2. Type Inference

Let TypeScript infer types when possible:

// Good
const numbers = [1, 2, 3]; // Type: number[]

// Bad
const numbers: number[] = [1, 2, 3];

Interface vs Type

When to Use Interfaces

interface User {
  id: number;
  name: string;
  email: string;
}

// Extending interfaces
interface AdminUser extends User {
  role: 'admin';
  permissions: string[];
}

When to Use Types

type Status = 'pending' | 'approved' | 'rejected';

type ApiResponse<T> = {
  data: T;
  status: number;
  message: string;
};

Advanced Type Patterns

1. Utility Types

// Partial
type PartialUser = Partial<User>;

// Pick
type UserCredentials = Pick<User, 'email' | 'password'>;

// Omit
type PublicUser = Omit<User, 'password'>;

// Record
type UserRoles = Record<string, string[]>;

2. Type Guards

function isAdmin(user: User): user is AdminUser {
  return 'role' in user && user.role === 'admin';
}

function processUser(user: User) {
  if (isAdmin(user)) {
    // TypeScript knows user is AdminUser here
    console.log(user.permissions);
  }
}

Error Handling

1. Custom Error Types

class ValidationError extends Error {
  constructor(public field: string, message: string) {
    super(message);
    this.name = 'ValidationError';
  }
}

function validateUser(user: User): void {
  if (!user.email.includes('@')) {
    throw new ValidationError('email', 'Invalid email format');
  }
}

2. Result Type Pattern

type Result<T, E = Error> = {
  success: true;
  data: T;
} | {
  success: false;
  error: E;
};

async function fetchUser(id: number): Promise<Result<User>> {
  try {
    const response = await api.get(`/users/${id}`);
    return { success: true, data: response.data };
  } catch (error) {
    return { success: false, error: error as Error };
  }
}

React with TypeScript

1. Component Props

interface ButtonProps {
  variant: 'primary' | 'secondary';
  size: 'small' | 'medium' | 'large';
  onClick: () => void;
  children: React.ReactNode;
}

const Button: React.FC<ButtonProps> = ({
  variant,
  size,
  onClick,
  children
}) => {
  // Component implementation
};

2. Custom Hooks

function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue] as const;
}

Testing with TypeScript

1. Jest with TypeScript

describe('UserService', () => {
  it('should create a new user', async () => {
    const user: User = {
      id: 1,
      name: 'John Doe',
      email: 'john@example.com'
    };

    const result = await createUser(user);
    expect(result).toEqual(user);
  });
});

2. Mocking

const mockApi = {
  get: jest.fn<Promise<User>, [string]>(),
  post: jest.fn<Promise<User>, [string, Partial<User>]>()
};

Conclusion

Following these TypeScript best practices will help you write more maintainable, type-safe, and robust applications. Remember that TypeScript is a tool to help you catch errors early and provide better developer experience, so use it wisely and consistently throughout your project.

Built with love by Manu Arora