← Back to all blogs

I Was Tired of Managing Enums in 5 Different Places

How one frustrating night led me to build a small util that changed how I handle enums across my frontend projects.

by Akinur Rahman

14/02/2026

I Was Tired of Managing Enums in 5 Different Places

I Was Tired of Managing Enums in 5 Different Places

Every frontend project I've worked on has the same problem. You have a status field (order status, student gender, institution type, whatever) and you need to use it everywhere.

And by everywhere, I really mean everywhere.


The Problem

Let me show you what I mean. Say you have this:

export const INSTITUTION_TYPES = {
  SCHOOL: 'SCHOOL',
  JUNIOR_COLLEGE: 'JUNIOR_COLLEGE',
} as const;

export type InstitutionType = (typeof INSTITUTION_TYPES)[keyof typeof INSTITUTION_TYPES];

Okay, that's fine for a type and a basic constant. But now your designer gives you a table and says "show a badge next to each institution type." So you write a helper somewhere:

function getInstitutionLabel(type: InstitutionType) {
  if (type === 'SCHOOL') return 'School';
  if (type === 'JUNIOR_COLLEGE') return 'Junior College';
}

Then your PM says "we need a filter dropdown for this." So now you write:

const institutionOptions = [
  { value: 'SCHOOL', label: 'School' },
  { value: 'JUNIOR_COLLEGE', label: 'Junior College' },
];

Then someone says the badge for JUNIOR_COLLEGE should be warning variant not default. So you go find wherever you're rendering that badge and change it.

Then two months later a new type gets added, say UNIVERSITY, and you have to find all these places. The constant. The label helper. The options array. The badge logic. The Zod schema. The TypeScript type.

You miss one. A bug goes to production. You spend 20 minutes confused before you realize you only updated it in four out of five places.

This happened to me one too many times.


What I Built

One night I just sat down and said okay, I'm done with this. Let me build something that keeps all of this in one place.

Here's what I came up with:

import { BadgeVariant } from '@ui/badge';
import z from 'zod';

export type LookupConfig<T extends string> = Record<
  T,
  {
    label: string;
    badgeVariant: BadgeVariant;
  }
>;

export function createLookup<T extends string>(config: LookupConfig<T>) {
  const entries = Object.entries(config) as [T, LookupConfig<T>[T]][];

  return {
    config,
    keys: Object.keys(config).reduce(
      (acc, key) => {
        acc[key as T] = key as T;
        return acc;
      },
      {} as Record<T, T>
    ),
    values: entries.map(([key]) => key),
    options: entries.map(([value, meta]) => ({
      value,
      label: meta.label,
    })),
    getLabel(value: T) {
      return config[value].label;
    },
    getBadgeVariant(value: T) {
      return config[value].badgeVariant;
    },
    toZodEnum() {
      return z.enum(Object.keys(config) as [T, ...T[]]);
    },
  };
}

And here's how you use it:

export const StudentGender = createLookup({
  MALE: { label: 'Male', badgeVariant: 'info' },
  FEMALE: { label: 'Female', badgeVariant: 'info' },
  OTHER: { label: 'Other', badgeVariant: 'info' },
});

export type StudentGenderType = keyof typeof StudentGender.config;

That's it. One object. Everything lives here.


What You Get Out of It

Once you define a lookup, you get all of this for free:

For your filter dropdown:

// Already shaped exactly how your select component wants it
StudentGender.options
// [{ value: 'MALE', label: 'Male' }, ...]

For your table badge:

<Badge variant={StudentGender.getBadgeVariant(row.gender)}>
  {StudentGender.getLabel(row.gender)}
</Badge>

For Zod validation:

StudentGender.toZodEnum()

For safe key references (no magic strings):

if (gender === StudentGender.keys.MALE) { ... }

For the TypeScript type:

export type StudentGenderType = keyof typeof StudentGender.config;

All from one definition.


Why This Actually Helps

The thing I like most about this isn't the API. It's that when something changes, whether a new value gets added, a label needs updating, or a badge variant needs to be different, I go to exactly one place. I make the change once and it's done everywhere.

Before this I was scared to add new enum values because I knew I'd forget somewhere. Now I just add it to the lookup and TypeScript tells me if I've missed handling it anywhere.

It's a small util. Probably 30 lines. But it's saved me a lot of frustration.