Sustainable string enums in Typescript
Tags: typescript, webdev, react, javascript
Published: 2021-05-18 16:28:59


So you've got an enum type in your database table. The user needs to be able to set that property via a dropdown in your web application. But it's possible the table will require more enum types in the future. How do we write a dropdown that remains as flexible as the enum type itself? 

## Step 1: Define our database

For this example we'll use a Post as our database table.

```sql
CREATE TYPE post_status AS ENUM ('published', 'draft', 'review');

CREATE TABLE Posts (
  Id int,
  Title varchar(255),
  Status post_status
);
```

A post starts as a draft, then moves to a review stage, and is finally published. Authors/editors of this post can move the post through these steps at any time through our UI.

## Step 2: Define our types

Let's start to map out our schema in the client side application. 

```typescript 
// types.ts
export enum PostStatus = {
  PUBLISHED = 'published',
  DRAFT = 'draft',
  REVIEW = 'review',
}

export interface Post {
  id: number;
  title: string;
  status: PostStatus;
}
```

## Step 3: Post management form

I'll skip typing out the full form. But let's assume you have a semantic form component that takes post information and allows the user to interact with it. The following would be the dropdown for changing the status:

```html
<select>
  <option>Published</option>
  <option>Draft</option>
  <option>Review</option>        
</select>
```

This is fine for our first iteration but what are some ways to make this more maintainable going forward?

### Option 1

Assuming all of the options have similar output we could do something like the following in react:

```typescript
// index.tsx
const StatusDropdown = () => {
  const renderOption = (text: string) => {
    return <option key={text}>{text}</option>
  }
  
  const options: string[] = ['Published', 'Draft', 'Review']

  return (
    <select>{options.map(renderOption)}</select>
  )
}

export default StatusDropdown;
```

To add a new option we just add a new string to the options array. That's not bad, but we still have to update the PostStatus type whenever the data schema is adjusted, along with adjusting this component. Is there a way to update the PostStatus type and have that propagate to this dropdown?

### Option 2

```typescript
import { PostStatus } from './types';
import { capitalize } from 'lodash';

const StatusDropdown = () => {
  ...
 
  const options = Object.values(PostStatus).map(
    (value: string) => capitalize(value))
  
  ...
}
```

Now we've got a dropdown that is always in sync with the current definition of PostStatus. Our overall change surface area is reduced and future developers have an easier time making changes. 

But why can we use the `Object` primitive and its functions? Well when an enum is evaluated at runtime it turns out to be an object! More details can be found [here](https://2ality.com/2020/01/typescript-enums.html#enums-at-runtime). 

While typescript can be a bit of a learning curve at first, I hope techniques like this help you keep your applications up to date. And who knows, maybe one day enums will have a way to return all of the string value types without using the Object primitive. Only time will tell!