Patrick Desjardins Blog
Patrick Desjardins picture from a conference

A Safe Way to Define Immutable Default Values in TypeScript

Posted on: 2022-02-24

Regardless of the framework you are using, you will end up having some area of code that relies on default values. When the state relies on an object, there is a way to ensure that the default values are not mutated in the future.

The simplest way to define a set of default values is to assign an object's value. TypeScript infers the object to be of two properties. The p1 to be a boolean and the p2 to be a number.

const defaultValues = { p1: true, p2: 100 };
defaultValues.p1 = false;

A step further is to ensure the type does not change and be as narrow as possible using as const. In that case, p1 type is not a boolean but true, and p2 is if type 100.

const defaultValues = { p1: true, p2: 100 } as const;
defaultValues.p1 = false; // Error!

That is great for design time, but JavaScript has the Object.freeze that ensure that at runtime you cannot change an object. Using the Object.freeze generates a type of ReadOnly<> between the inferred type.

const defaultValues = Object.freeze({ p1: true, p2: 100 });
defaultValues.p1 = false; // Error!

This is the same as:

interface MyValueType {
  p1: boolean;
  p2: number;
}
const defaultValues: Readonly<MyValueType> = Object.freeze({ p1: true, p2: 100 });
defaultValues.p1 = false; // Error!

I like the explicit interface because we can use it around the application for a parameter. However, also possible to extract the type using typeof.

type MyValueType = typeof defaultValues;

But, that is not right. We cannot use that type around the application because the extraction results to the type:

interface MyValueType {
  readonly p1: boolean;
  readonly p2: number;
}

A more accurate extraction would be to use a mapped type.

type UnReadonly<T> = { -readonly [P in keyof T]: T[P] };
type MyValueType = UnReadonly<typeof defaultValues>;

The right approach is the one you defined with your team. Regardless, what is interesting is that you can have a safe type at runtime and design time and still be able to use the type in mutable are of your code when needed.