5 reasons why destructuring and inline types hurt your TypeScript codebase
Tags: programming, typescript, antipattern
Published: 2022-10-09 19:42:34

---
title: 5 reasons why destructuring and inline types hurt your TypeScript codebase
published: true
description: I want to show you how using destructuring and inline types in TypeScript makes your codebase less readable!
tags: programming, typescript, antipattern
canonical_url: https://cichocinski.dev/blog/5-reasons-why-destructuring-hurt-your-typescript-codebase
---

Recently I've seen a tweet by Jamie Kyle about using destructuring, default params, and inline types:

{% twitter 1577030375158607872 %} 

That tweet and a few React components I have seen recently in my day job inspired me to write this blog post. I want to show you how using destructuring and inline types makes your TypeScript less readable!

## How TypeScript function definition look like?

In JavaScript and TypeScript, you can define a function either using `function` keyword or lambda/arrow-function. Both ways are valid, but have their differences. Let's take a look at the simple `sendMessage` function. Implementation logic is not relevant to us.

```typescript
// sendMessage function written using `function` keyword
function sendMessage(message: string) {
  // function logic
}

// same sendMessage written as arrow function
const sendMessage = (message: string) => {
  // function logic
};
```

When function definition is quite simple, the function accepts a few params of a different type. If they are primitives like strings or numbers, everything is readable.

Let's say you want to pass some additional information alongside your message content to the `sendMessage` function.

```typescript
function sendMessage(message: {
  content: string;
  senderId: string;
  replyTo?: string;
}) {
  // you can assess content using `message.content` here
}
```

As you can see, TypeScript allows you to write an inline type definition for the `message` object you want to pass without specifying the type using `type` or `interface` keyword.

Let's add destructuring. When you pass a large `message` object to your function, TypeScript allows breaking apart passed arguments to reduce the code boilerplate of repeating `message` variable many times.

```typescript
function sendMessage({
  content,
  senderId,
  replyTo,
}: {
  content: string;
  senderId: string;
  replyTo?: string;
}) {
  // you have access to `content` directly
}
```

## Why I think it's a bad idea and how you can make it better

It may look like a nice idea, after all, you don't need to write `message` that many times, right? It turns out it's not that great. Let's talk about 5 reasons why I think it's an antipattern.

### 1. You're not sure where your data is coming from

When you're reading the function body, and you see `senderId` you have to double-check to be sure from where that function comes. Is it passed as an argument or calculated somewhere in the function?

### 2. It's hard to write documentation

There is no natural place to write documentation comments when all the types are cramped with destructuring in the function definition. You could write comments between each type field, but that makes the whole function definition even longer. It's actively discouraging you from writing a quick summary of the data you're passing.

### 3. It's hard to pass that data forward

When your data is destructured you need to structure it again into a new object if you want to pass it forward. This discourages creating smaller helper functions and relying on composition to build up your main function logic.

### 4. You cannot reuse argument types outside this function

If you need to reuse your function arguments in helper functions when composing your main function logic, you must type the same set of types repeatedly. This makes it easier not to write types at all.

### 5. It just takes a lot of space

Let's face it. It's just a lot of lines of code that take up a lot of screen space. And in addition, it focuses on implementation detail – the inner type of the arguments you are passing to a function, which is most of the time not relevant when you're looking at that function.

## Just create type for it

Extracting the type and placing it right above the function makes it much more readable. There is a place for documentation comments, you can reuse that type in some other helper function and change the type definition in one place if needed.

```typescript
/**
 * Message to send using XYZ API
 */
export type MessageToSend = {
  /**
   * Markdown string of the user's message
   */
  content: string;
  /**
   * Id of the sender user
   */
  senderId: string;
  /**
   * Other message ID if this is a reply
   */
  replyTo?: string;
};

function sendMessage(message: MessageToSend) {
  // function logic
}

function getUserIdsToNotify(message: MessaageToSend) {
  // function logic
}
```

## Resources

List of resources I used when researching this blog post:

- [https://www.typescriptlang.org/docs/handbook/variable-declarations.html#object-destructuring](https://www.typescriptlang.org/docs/handbook/variable-declarations.html#object-destructuring)

---
Read more at [cichocinski.dev](https://cichocinski.dev)