Simple Practical ways you can use Typescript Enums in your code.
The purpose of this guide is to explore Typescript Enums, outlining their definition, types, advantages, practical uses, common pitfall, and some best practices.
As a fellow developer, that works across multiple teams and various projects as my career advances. Enums have come to become a steady staple in my project boiler plates… Infact, way back when I started using it, I started with just adding them into my constants.ts file.. soon after, I moved on to creating a file to accomodate all my enums — nowadays, depending on the complexity of some projects, I find myself creating a folder for enums based on different used case to simplify its use project-wide.
What are Typescript Enums?
Enums (short for Enumerations), are a way to define a collection of related values as named constants. They provide a more readable and manageable way to work with a limited set of values, improving code clarity and preventing errors caused by typos or magic numbers.
They are particularly useful when you have a fixed set of values that are logically grouped together, such as days of the week, status codes, or user roles.
Typescript supports three types of enums:
- Numeric Enums: These are the simplest type, where enum members are automatically assigned numeric values, starting from 0 by default.
enum Status {
Inactive, // 0
Active, // 1
Pending, // 2
}
console.log(Status.Inactive); // Output: 0
OR
enum Status {
Pending = 1,
Approved = 2,
Rejected = 3,
}
console.log(Status.Approved); // Output: 2
- String Enums: With string enums, each member has a string literal as its value. This offers better readability and debugging compared to numeric enums.
enum UserRole {
Admin = "ADMIN",
Editor = "EDITOR",
Viewer = "VIEWER",
}
console.log(UserRole.Admin); // Output: ADMIN
- Heterogeneous Enums: These enums can mix numeric and string values, though they are less common and generally less recommended due to potential confusion.
enum MixedEnum {
Yes = 1,
No = "NO",
}
Why Enums?
When I first learned about enums, I asked this question myself, and I find several devs ask the same question when I talk about its necessity when it comes to medium/large projects — WHY USE ENUMS?
Below, I’ll try to itemize what I think to be some of benefits it offers:
- Readability: Enums make code more self-documenting by replacing cryptic numeric or string literals with descriptive names. Instead of using
1
for "Active" and0
for "Inactive", you can useStatus.Active
andStatus.Inactive
. - Type Safety: Enums help prevent errors by ensuring that only valid values are used. TypeScript’s type system will catch assignments of incorrect values to enum variables.
- Maintainability: If a value needs to be changed, you only need to update it in one place — the enum definition — rather than searching throughout your codebase.
I know, the next question is: “So I can use plan JS arrays and objects to achieve similar purposes — Why use enums?”
While you can use an object like While you could use an object likeconst Status = { Active: 1, Inactive: 0 }
, this doesn't enforce that only these values are used. Enums provide that guarantee.
In simple terms, enums provide type safety and better integration with TypeScript’s type system.
Common Practical “use-cases” for Enums
1. Managing Application States
They are ideal for representing application states, such as the state of a network request.
enum LoadingState {
Initial,
Loading,
Loaded,
Error,
}
let currentState: LoadingState = LoadingState.Initial;
// ... later in your code ...
currentState = LoadingState.Loading;
// ... data fetching ...
currentState = LoadingState.Loaded;
2. Switch-case Statements
They also make switch-case statements cleaner and more readable.
enum LogLevel {
Error = "ERROR",
Warn = "WARN",
Info = "INFO",
}
function logMessage(level: LogLevel, message: string) {
switch (level) {
case LogLevel.Error:
console.error(message);
break;
case LogLevel.Warn:
console.warn(message);
break;
case LogLevel.Info:
console.log(message);
break;
}
}
logMessage(LogLevel.Error, "Something went wrong!");
3. Handling Configuration Options
Enums are a great way to define configuration options in your code base. A common example for a react native developer would be defining environment variables.
enum Environment {
Development = "DEV",
Staging = "STAGING",
Production = "PROD",
}
function getApiUrl(env: Environment): string {
switch (env) {
case Environment.Development:
return "https://dev.api.example.com";
case Environment.Production:
return "https://api.example.com";
default:
throw new Error("Invalid environment.");
}
}
4. Role-Based Access Control
Enums provide an excellent way to define user roles and permission in an application. By using enums, you can streamline access control logic and ensure consistency across your codebase.
enum UserRole {
Guest,
Editor,
Admin,
}
function checkPermission(userRole: UserRole, requiredPermission: UserRole): boolean {
switch (requiredPermission) {
case UserRole.Guest:
return true; // Everyone can access guest features
case UserRole.Editor:
return userRole === UserRole.Editor || userRole === UserRole.Admin;
case UserRole.Admin:
return userRole === UserRole.Admin;
default:
return false;
}
}
const currentUserRole = UserRole.Editor;
if (checkPermission(currentUserRole, UserRole.Admin)) {
// Access granted
} else {
// Access denied
}
5. Handling of Server Errors
Enums can as well be used to categorize and manage server error codes or messages, providing a structure and consistent approach to error handling and debugging.
enum ErrorCode {
NotFound = 404,
Unauthorized = 401,
ServerError = 500,
BadRequest = 400,
}
function handleError(code: ErrorCode) {
switch (code) {
case ErrorCode.NotFound:
console.error("Resource not found.");
break;
case ErrorCode.Unauthorized:
console.error("Unauthorized access.");
break;
case ErrorCode.ServerError:
console.error("Internal server error.");
break;
case ErrorCode.BadRequest:
console.error("Bad request.");
break;
default:
console.error("An unknown error occurred.");
}
}
handleError(ErrorCode.NotFound); // Output: Resource not found.
6. Defining Routes in Next.js Projects
For someone that works on projects using file-based routing such as Next.js or Expo router, one major challenge that might arise as the project expands is managing route names especially if you’re working with several other developers.
Enums can be used in this case scenarios to define route links, ensuring consistency & reducing the risk of typos in the route paths.
enum AppRoute {
Home = "/",
About = "/about",
Contact = "/contact",
Product = "/product/[id]", // Dynamic route
}
// Usage:
import Link from 'next/link';
<Link href={AppRoute.Product.replace("[id]", "123")}>Product 123</Link>
function navigateTo(route: AppRoutes) {
console.log(`Navigating to ${route}`);
}
navigateTo(AppRoutes.About); // Output: Navigating to /about
7. Managing Mutation and Query Keys for Async Actions
When working with libraries like React Query or SWR, enums can be used to define mutation and query keys for async actions. This ensures consistency and avoids hardcoding strings throughout your code. They ensure that your keys are centralized and easy to update.
enum QueryKey {
Products = "products",
User = "user",
Cart = "cart",
}
enum MutationKey {
AddToCart = "addToCart",
UpdateUser = "updateUser",
Checkout = "checkout",
}
// Usage (example with React Query):
useQuery({
queryKey: [QueryKey.Products],
queryFn: fetchProducts
});
useMutation({
mutationKey: [MutationKey.AddToCart],
mutationFn: addToCart
});
Advanced Enum Features
Typescript enums offer more advanced feaures that can significantly enhance your code flexibility, performance, and maintainability. As your gain more experience in typescript, you’ll likely encounter scenarios where basic enum types can sometimes feel a bit limiting.
Maybe you need to combine different flags using bitwise operations (e.g., file access permissions), or you want to generate enums values dynamically at runtime, or maybe you’re even working with a performance-sensitive part of your application and need the compiler to optimize enum usage…
These more complex situations are where advanced enum features come in handy. Say, you’re trying to fetch configuration options from an api, and you want to create an enum based on the values returned from the api, which requires computed enum members.
Enum Member Types and Computed Enums
Enum members can have computed values, though this is less common.
enum FileAccess {
// Constant member
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// Computed member
Gated = "123".length, // Value becomes 3
}
Reverse Mapping in Numeric Enums
Numeric enums support reverse mapping, allowing you to get the enum name from its value.
enum Status {
Active,
Inactive,
}
let status = Status.Active; // 0
let statusName = Status[status]; // "Active"
Const Enums
Const enums are inlined at compile time, reducing runtime overhead, thereby improving performance
const enum Directions {
Up,
Down,
Left,
Right,
}
let direction = Directions.Up; // Inlined: let direction = 0;
Common Pitfalls and Best Practice
Potential Issues
- Overusing Enums: Enums are not always necessary; sometimes, a union type or JS Object works just fine. A scenario would be when it’s just using within a local context.
- Avoid Heterogeneous Enums: They can be confusing and are often unnecessary, and how I see it — The moment you are considering suing Heterogeneous enums, there are better ways the type can be handled
Best Practices
- Name Enums Clearly: Choose descriptive names that reflect the purpose of the enum.
UserStatus
is better than justStatus
. - Organize Enums Logically: Group related enums together in appropriate files or modules.
- Use String Enums when possible: They offer better readability and avoid reverse mapping issues.
- Use const enums for performance-critical code.
In conclusion, TypeScript enums are a versatile and powerful tool for managing related values in your code. They improve readability, type safety, and maintainability, making them a valuable addition to any TypeScript project. By understanding how to declare and use enums effectively, you can write cleaner, more robust code.