Matt's Tidbits #95 - Defining constants in TypeScript
Tags: mattstidbits, typescript, tidbittuesday
Published: 2021-05-11 12:13:29

[Last time I wrote about how to write safe enums in TypeScript.](https://medium.com/nerd-for-tech/matts-tidbits-94-writing-safe-enums-in-typescript-2955706a1e5c) This week's tidbit is also TypeScript related - how to define constants!

Defining a constant in TypeScript can be pretty simple - you can just write something like `const SOME_CONSTANT="FOO";`. However, that `const` keyword only works if you're defining variables at file-scope or inside a function. What if you want to group your constants under some kind of named object (as is common in other languages such as Java, C++, etc.)

The obvious way (and what I did to start), was simply to define an object to contain my constants, like this:
```ts
const NamedVariables = {
  SOME_CONSTANT_1: "FOO",
  SOME_CONSTANT_2: 42,
  DEFAULT_FONT_WEIGHT: "700",
};
```

This allows me to use this like this: `NamedVariables.SOME_CONSTANT_1`. Great!

However, there's one tiny gotcha. This works fine for some situations, but in cases where TypeScript is expecting a value that's part of a union type, such as specifying the `fontWeight` property of a React Native `TextStyle`, you may see an error such as this:
```
Type 'string' is not assignable to type '"normal" | "bold" | "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900" | undefined'.
```

If you're like me, you might scratch your head over this for quite a while. Until you realize (do some fervent Google searching) that Object properties are not constant by default.

The `const` in `const NamedVariables` above means that the reference to that object is constant - so it would be illegal to do something like `NamedVariables = <something else>` after it's already been defined. Which is good - we definitely don't want someone to reassign our constants object. But how can we define its properties as constant?

The first thing I found is to use [`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) - this returns an object whose properties are read-only. So, if we try this with our example:
```ts
const NamedVariables = Object.freeze({
  SOME_CONSTANT_1: "FOO",
  SOME_CONSTANT_2: 42,
  DEFAULT_FONT_WEIGHT: "700",
});
```

Unfortunately, this STILL doesn't work if we try to use `DEFAULT_FONT_WEIGHT` in a union type. Why is this? As best as I can understand, it's because TypeScript doesn't recognize this as a true constant - Object.freeze() makes use of some somewhat crazy runtime checks that throw an error if you try to modify it. So, chalk this one up to a limitation (maybe temporary? maybe permanent?) of TypeScript.

So… is there any way to achieve this? The good news is, yes!

Here's how we can make all properties on an object be treated as constants:
```ts
const NamedVariables = {
  SOME_CONSTANT_1: "FOO",
  SOME_CONSTANT_2: 42,
  DEFAULT_FONT_WEIGHT: "700",
} <b>as const</b>;
```

That's it - those two little keywords at the end [`as const`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) - are a TypeScript addition (not part of JavaScript) that instruct the type checker that all properties on that object should be treated as constants.

I highly recommend adding this to all of your Objects that you intend to be constants. Let the type system help you!

Do you define constants differently? I'd love to hear from you in the comments!

Interested in working with me in the awesome Digital Products team here at Accenture? [We're hiring!](https://www.intrepid.io/careers)