TypeScript Patterns to Level Up Your Game
Tags: typescript, fullstack, javascript
Published: 2024-04-10 14:11:21

Have you mastered the basics of TypeScript, and are wondering what to learn next? I was in the same position not long ago. Here are a few type patterns that will help you write high quality code and improve your productivity. I wish someone had showed me these when I was trying to improve my skills!

### **1\. Partials**

The `Partial<T>` utility type enables you to create a type with all properties of `T` set to optional. This is particularly useful when you want to partially update or copy an object.

```typescript
interface User {
  id: string;
  name: string;
  age: number;
}

// `changes` can be a combo of any property in `User`
function updateUser(id: string, changes: Partial<User>) {
  if (changes.name) {
    // Implementation to update name...
  }
  if (changes.age) {
    // Update age...
  }
}
```

Using `Partial<User>` allows for the `updateUser` function to accept an object that may contain any subset of the properties defined in the `User` interface.

### **2\. Records**

`Record<K,T>` is a utility that constructs a type with a set of properties `K` of type `T`. This utility is ideal for mapping properties to values of a certain type, creating lookup tables, or any scenario where you need a more flexible yet strongly-typed object structure.

```typescript
type CatInfo = {
  age: number;
  breed: string;
}

type CatTracker = Record<number, CatInfo>; // Cat ID to cat attrs

let cats: CatTracker = {};
// This is fine
cats[10] = { age: 5, breed: 'British Shorthair' }; 
// Error! 'tom' is not a number
cats['tom'] = { age: 5, breed: 'British Shorthair' };
```

One way I use the `Record` type is to organize records pulled from a database when building a service API.

### **3\. Discriminated Unions**

What if you know exactly how your data should look ahead of time, and you want to limit the number of safe options?

Discriminated unions help you to do just that. This pattern is incredibly useful for state management and handling complex data structures safely.

```typescript
// Define loading states as discriminated union
type LoadingState =
  | { status: 'loading' }
  | { status: 'success', data: string }
  | { status: 'error', error: Error };

function handleState(state: LoadingState) {
  switch (state.status) {
    case 'loading':
      // Handle loading...
      break;
    case 'success':
      // Handle success - `data` is guaranteed to be present
      const parsed = JSON.parse(state.data);
      // More logic...
      break;
    case 'error':
      // Handle `state.error`...
      break;
  }
}
```

### **4\. Object Keys**

The `keyof` operator in TypeScript is used to create a union type consisting of all the public property keys of a given type. Use it when you want to ensure that a string argument is a valid key for a given object type.

```typescript
interface Person {
  name: string;
  age: number;
  hasPets: boolean;
}

// Using 'keyof' to create a union type of the keys of Person
type PersonKeys = keyof Person;

// PersonKeys is equivalent to: 'name' | 'age' | 'hasPets'

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const guy: Person = {
  name: 'John',
  age: 30,
  hasPets: true,
};

// Example usage
const nameKey: PersonKeys = 'name';
const personName: string = getProperty(guy, 'name'); // 'John'
```

### **5\. Read-Only Properties**

The `Readonly<T>` utility type in TypeScript makes all properties of a type `T` immutable (in other words, cannot be reassigned after their initial declaration). This is particularly useful when you want to create a type that should not be modified after creation.

```typescript
interface User {
  name: string;
  age: number;
}

// Making User properties read-only
const user: Readonly<User> = {
  name: 'Alice',
  age: 30
};

// If `user` wasn't created as `Readonly`, the properties below would be mutable.

user.name = 'Bob'; // Error: Cannot assign to 'name' because it is a read-only property.
user.age = 25; // Error: Cannot assign to 'age' because it is a read-only property.
```

### **In Conclusion**

These intermediate-level type demos above are super practical for everyday TypeScript usage. And this is just a sampling of everything that TypeScript can do. Check out the [TypeScript docs](https://www.typescriptlang.org/docs/handbook/utility-types.html) to see all of the utility types available.

All in all, the more logic you can move into the type checker, the safer and more maintainable your code will be. Your team (and your future self) will thank you!

*For more insights into TypeScript, full-stack development, and beyond, don't forget to* [*subscribe to my newsletter*](https://jaydevm.hashnode.dev/newsletter)*!*