# TypeScript

TypeScript provides many benefits over vanilla JavaScript, which we embrace wholeheartedly:

  • Type safety checking at compile time
  • Vastly improved autocomplete in most code editors
  • Easier refactoring and renaming
  • Clearer communication about what code is intended to handle

This does come at the cost of some additional complexity, but we consider the benefits well worth the cost.

# type vs interface

Composing and inheriting types is a powerful part of TypeScript.

When declaring a standalone type, prefer using type instead of interface. Most type declarations can be handled perfectly adequately with type. Use an interface only if you really need to use the extends keyword to extend an existing type, which is basically never.

If you need to add properties to a type, use the intersection operator (&) instead of extending the type. If you want to change the type of a property, use the Omit<> utility function with the & intersection operator to get the correct result. This should work for most cases.

Examples:
// ✅ Good: defines a type
type CustomerDebitCard = {
  cardNumber: string;
  accountName: string;
  cardName: string;
  status: CustomerDebitCardStatus;
  spendingLimit: string;
  atmWithdrawalLimit: string;
  availableBalance: string;
  note?: string;
  brand?: string;
};

// ✅ Good: Uses `Omit<>` to remove the property we want to change,
// then uses the `&` intersection operator to add back in the types we want.
type FlatCard = Omit<CustomerDebitCard, "brand"> & {
  brand: BrandName;
  originalIndex?: number;
};

// ❌ Bad: avoid using `interface` like this.
interface FlatCard extends CustomerDebitCard {
  brand: BrandName;
  originalIndex?: number;
}

// ✅ This is appropriate, but should be used rarely: only if we really
// need to extend another interface. (Note that `AriaCheckboxProps` does
// not contain any of the props we're adding here.) This is generally
// only used in library code such as Vinyl.
interface CheckboxProps extends AriaCheckboxProps {
  description?: string;
  registerOptions?: RegisterOptions;
  children?: React.ReactNode;
}

Types declared with type and interface behave the same when used with type utility functions such as Pick<>, Omit<>, and others. So why prefer declaring them with type instead of interface?

  • Clearer semantics: interface is typically a word associated with classes. A class implements or extends an interface. This isn't the paradigm we use with TypeScript.
  • Type visibility: types declared with type are often more transparent in Intellisense/code completion.

# Setting State Variable Utilizing Type Inference

To create a state variable in React with type T | undefined without explicityly setting the inital value to undefined or explicitly typing it as such, you can utilize type inference by omitting the initializer and providing a type parameter to useState.

Examples:
// ✅ Good: Omit the initializer, and React will infer
// the initial state as undefined because no value is provided.
const [name, setName] = useState<T>();

// ❌ Bad: avoid typing like this.
const [name, setName] = useState<T | undefined>(undefined);

This approach is clean and avoids unnecessary verbosity.