When to use Enums in Typescript

Typescript

06 Apr 2020 | 6 minute read

Typescript gives great flexibility when defining interfaces and there are multiple ways of implementing the same thing. One such thing is working with constants in interfaces.

In my experience, this is one of the cases where enums come in handy. They give us a way to reuse constants throughout our codebase which increases readability and reduces the need to repeat ourselves.

Let's say you're building an e-commerce platform, and that your application should handle both business-to-business (Professional) and business-to-consumer (Private) customers. As these customer segments should be treated somewhat differently in your application, you need to be able to identify which group a user belongs to.

As of this, you have the type attribute in your Customer interface.

interface Customer {
  type: 'Private' | 'Professional';
  firstName: string;
  lastName: string;
  email: string;
}

This gives us an interface that only accepts Private or Professional for the type property.

If we try to have any other value for the type property, Typescript will tell us that the expected type for the property type is invalid.

const customer: Customer = {
  type: 'Jedi',
  firstName: 'Oscar',
  lastName: 'Alsing',
  email: '[email protected]',
};
Type '"Jedi"' is not assignable to type '"Private" | "Professional"'

Earlier I mentioned that our application might want to treat Private and Professionals user differently. One such application might be to offer discounts to Professional users as they often buy in bulk. When calculating the price for your customer, you might implement a function that checks the type of the customer, and displays a different price.

Note: Prices are usually calculated on the backend, and it's probably not a great idea to calculate the pricing tiers on the frontend in your production application.

const priceForCustomer = (defaultPrice: number, customer: Customer): number => {
  switch (customer.type) {
    case 'Private':
      return defaultPrice;
    case 'Professional':
      return defaultPrice * 0.7;
    default:
      return defaultPrice;
  }
};

Neat! But as our codebase grows, there will be more and more snippets in our code checking the type property of the customer to see whether the user is a Private or Professional user.

For some reason, we decide to change the naming conventions of the Customer type from Private och Professional to B2C and B2B.

But as we've used the Private and Professional throughout our application, we have no other choice but to change all of our Private and Professional. If your application is large, this could be non-trivial.

What we could have done is to use Enums. It would have enabled us to used named constants, which would have helped us when refactoring our application, as well as increasing readability of our code. Using enums, our interface could look like this:

enum CustomerType {
  Private = 'Private',
  Professional = 'Professional',
}

interface Customer {
  type: CustomerType;
  firstName: string;
  lastName: string;
  email: string;
}

In this example, I've used string enums instead of numeric enums, as it makes more sense in terms of what the data represents.

As type property of our Customer interface now accepts the CustomerType enum, we now have two choices when working with the type of a Customer.

const priceForCustomer = (defaultPrice: number, customer: Customer): number => {
  switch (customer.type) {
    case 'Private': // Using the accepted values of the enum.
      return defaultPrice;
    case 'Professional':
      return defaultPrice * 0.7;
    default:
      return defaultPrice;
  }
};

Or using the CustomerType enum:

const priceForCustomer = (defaultPrice: number, customer: Customer): number => {
  switch (customer.type) {
    case CustomerType.Private: // Using the enum named constants.
      return defaultPrice;
    case CustomerType.Professional:
      return defaultPrice * 0.7;
    default:
      return defaultPrice;
  }
};

If we then want to refactor from Professional and Private to B2B and B2C in our Customer interface, all we need to do is to update the CustomerType constant values.

enum CustomerType {
  Private = 'B2C',
  Professional = 'B2B',
}

Since we are now using CustomerType.Private and CustomerType.Professional instead of Private and Professional, in the priceForCustomer function we don't have to do any other changes.

If you want to dig deeper into enums, the official documentation is great, and this article provides a lot of information as well.