TypeScript Done Wrong
Tags: typescript, javascript, tutorial
Published: 2021-10-20 06:01:12

_by author [Fernando Doglio](https://blog.openreplay.com/authors/fernando-doglio)_

Writing TypeScript code feels great because you're protected by the compiler. Every time you transpile the code, it gets checked for any and all potential mistakes you've made. It's literally making you a better developer by helping you avoid simple bugs.
That's amazing!

However, if you're not careful enough, you can still force those mistakes and introduce bugs by incorrectly using some of the language's features. 
That's right buddy, TypeScript can be a double-sided blade if you're not careful!
So let's check out some mistakes you're probably making right now.


## Overusing the `any` type
This one is a classic, especially for newcomers to the language, because they see it as a way of saying *"I have no idea what's going to be inside this variable, so just let me use it alright?"*.

That happens because we think type declaration has to be done when you declare the variable and at that time, you may not know exactly the shape of the data you'll be dealing with. This is especially true if you're dealing with returned information from outside resources such as an API call, or perhaps after parsing a JSON string.
But the key to solving this problem is not using the wildcard type `any`, instead is to understand that you can let TypeScript know that you _don't know the type yet_ but you will soon.

Imagine, if you will, the following very silly example:

```typescript 
let something: any;
let condition = true;

if(condition) {
  something = JSON.parse('{ "name": "Fernando Doglio", "age": 37 }');
} else {
  something = JSON.parse('{ "race": "dog", "number_of_legs": 4, "sex": "Male"}')
}

console.log(something)
```

We could be dealing with the string being parsed coming from a file read or from user input. We don't really know the same it'll take, we just know that sometimes it'll be talking about a person and others it'll talk about an animal. So the shape of `something` will be different depending on some logical `condition`. 
This is wrong.
Why? Because now we can't do any proper checks with `something`. If we wanted to reference one of its properties, it would be of type `any` by default, so really, any type of logic depending on `something` would automatically have to avoid type checking.
We've created a cascade effect that is nullifying TypeScript's main feature.

Feel good about yourself already?

So instead of relying on the pesky `any` type, you can define `something` to be of type `unknown` which is kind of the same, but not exactly.
 
By defining your variable with that type, you're telling TypeScript that you don't know the type _at that precise moment_ but that you will in a second. So instead, you could re-write the above code like this:

```typescript
type  Human = {
  name: string;
  age: number;
}

type Animal = {
  race: string;
  number_of_legs: number;
  sex: 'Male' | 'Female';
}

function parseit(str: string): unknown {
  return JSON.parse(str);
}

let something;
let condition = true;

if(condition) {
  something = parseit('{ "name": "Fernando Doglio", "age": 37 }') as Human;
  console.log("This is a person, named: ", something.name)
} else {
  something = parseit('{ "race": "dog", "number_of_legs": 4, "sex": "Male"}') as Animal;
  console.log("This is a ", something.race)
}
```
Key changes on the code:

* I've moved the parsing into a separate function that returns an `unknown` type. 
* I've defined 2 different types (`Human` and `Animal`). They're both potential types for `something` but we just don't know which ones until the code runs.
* I've declared `something` without a type, this is key.
* The logic of how you use `something` depending on its type needs to be inside each logical branch (i.e inside each block of the `if` statement).

With that out of the way, you can see how we've used the same variable to host 2 very different types of objects. This allows us to write code that is not type-checked during compilation (because it can't be checked then) but it does get checked during runtime.
 

## Using the Function type instead of specifying the signature
Another very common situation is when dealing with closures or callbacks. We have to define a type for them, but using `Function` as that type is not enough.
If we do that, then TS assumes we're dealing with a function that accepts `any` parameter and returns `any` as a result. 
Essentially we're back in the previous example, having completely obliterated the main benefit of TS.

In other words, you could write code like this:

```typescript
function myMainFN(callback: Function) {
  let date = new Date();

  callback(date, "random string", 131412);
  let i = callback("this other string");
  console.log(i, date, callback(123125091))
}
```

Notice how I'm calling `callback` in 3 different ways. I'm even declaring a variable with the return of one of those calls. Care to guess the type of `i` ? That's right, it'll be `any`. 
Instead, take the time to understand the signature of the functions you'll want to allow here. The callback is probably meant to receive some very specific parameters and to return a very specific type. So define it damn it!

```typescript
type myCallback = (error: Error|null, results: string) => void;

function myMainFN2(callback: myCallback) {
  let date = new Date();

  //your logic here...

  if(error) { //valid uses of callback
    callback(new Error("Something went terribly wrong, abort, abort!"), "");
  } else {
    callback(null, "This is fine");
  }


  //callback(date, "random string", 131412); - invalid because `date` is the wrong type and it also has 1 extra parameter
  //let i = callback("this other string"); - invalid because it's missing the first parameter 
  //console.log(i, date, callback(123125091)) - invalid because we're calling it with the wrong parameter
}
```

The first thing I did was I declared the callback's type: `myCallback`. I'm declaring it as a function that accepts 2 parameters, the first one potentially being `null` since I'm going with the "Error-first" pattern for this callback. And the second one being just a string.
Suddenly all my previous calls to the callback are invalid and TS can tell me that. Fantastic!
By defining the signature of the callback function I'm able to write code that properly relies on it and it's safe to do so, and I've also managed to document the way anyone using my function needs to structure their callback function. Win-Win!

## Open Source Session Replay
Debugging a web application in production may be challenging and time-consuming. [OpenReplay](https://github.com/openreplay/openreplay) is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue.
It’s like having your browser’s inspector open while looking over your user’s shoulder.
OpenReplay is the only open-source alternative currently available.

![OpenReplay](https://raw.githubusercontent.com/openreplay/openreplay/main/static/replayer.png)

Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay).


## Assuming that object literals are inferred types

TypeScript has this very interesting feature called "type inferring" in which you're allowed to skip type declaration in some situations, and given the type of data you're using, it'll infer it for you.
For example:

```typescript
let myValue = 2;
let mySecondValue = "This is a string";
let result = 2 - mySecondValue;
```
That code would work, just like that, in JavaScript and it would return a value. It wouldn't make any sense, because the actual operation makes no sense so you'd get a `NaN` as a result. But still, the interpreter would run it.
However, the second you throw that code at TS, it'll scream at you because it has already inferred that `myValue` is of type `number` and `mySecondValue` is of type `string` and you can't subtract a `string` from a `number` it's that simple!

Now, this works great with basic types, but not so much with object literals. With them, you can make mistakes without even knowing. How many times have you forgotten about writing a property? It happens, and TS will let you know about it, but only when you try to force a type into it, which is usually when you either pass it as a function parameter, or when you try to make it interact  with other objects that DO have a type.
Let's look at a quick example:

```typescript 
type  Human = {
  name: string;
  age: number;
}

type Animal = {
  race: string;
  number_of_legs: number;
  sex: 'Male' | 'Female';
  owner: Human;
}

let me = {
  name: "Fernando Doglio",
  age: 37
}

let myDog = {
  race: "Dog",
  number_of_legs: 4,
  sex: "Male",
}
```
Alright, so we've defined 2 object literals that somewhat follow the structure of both types `Human` and `Animal` but of course, just because they _look like them_ doesn't mean TS will assume you're wanting them to follow their structure. This is syntactically valid code and we have no errors so far.
Let's move on with the example:

```typescript
function whosYourOwner(a: Animal): string|null {
  if(a.owner) {
    return a.owner.name
  }
  return null;
}


whosYourOwner(myDog) //BOOM
```

And here is where the problems begin. The call to `whosYourOwner` will not work, because you now have 2 issues with your objects. Can you guess what they are?
We're coercing our `myDog` object literal into an `Animal`, and we're seeing that:

* On one hand, we're missing the `owner` attribute. Whoops! No worries though, we'll fix it quickly.
* But even if we do, we also have a problem with the `sex` property, because `string` (which is the inferred type for this property) is not going to be compatible `enum 'Male'|'Female'`. 

We're in trouble. However, the solution is very simple: assign a type of your object literal during declaration.

```typescript 
let myDog: Animal = {
  race: "Dog",
  number_of_legs: 4,
  sex: "Male",
};
```

Now 2 things will happen:

* First you'll hear from TS right there and now that you're missing the `owner` property. It's not marked as optional on the type declaration, so it'll be required here.
* The value of `Male` will be a valid option for the enum on `sex`.

In other words, type inferrence:

* For basic types: **good**
* For complex types: **not good**


## Numeric enums are bad - string enums good
This one is quick, but it can save you a huge headache and hours of debugging.
How do you declare your enums in TS? I mean, the whole point of them is to forget about their values, and directly use the enum properties, if you will. Right?
So let's pretend we're creating an enum around your residency status (i.e if you're a citizen, a resident in your country or if you're working with a Visa):

```typescript 
enum ResidencyStatus {
  Resident,
  Citizen,
  VisaWorker,
}

let myStatus: ResidencyStatus;

myStatus = ResidencyStatus.Resident;

console.log(myStatus)
```
The above code would work, and what would be the output of that last line? 
That's right, it would be `0`, that's because TS is gracious enough to assign a value automatically for us when we don't specify one. This is great and it shows how irrelevant the actual values are inside the enums.
What's the problem with this?
Easy, the following code is completely valid for TS as well:

```typescript 
let myStatus: ResidencyStatus;

myStatus = 124124;

console.log(myStatus)
```

See the problem? I've defined a variable of type `ResidencyStatus` which by definition, should let me limit the values I assign to it, but I can still assign any numeric value and TS won't know the difference. 
Can we solve this? YES we can, and it's not that complicated, we just need to assign string values to our elements inside the enum, and suddenly TS will become a lot more reserved about the type of value we assign to our variable:

```typescript 
enum ResidencyStatus {
  Resident="res",
  Citizen="cit",
  VisaWorker="visa",
}

let myStatus: ResidencyStatus;

myStatus = "123";

console.log(myStatus)
```

Now that code won't work, because TS will not like you assigning `"123"` to your property. 
Problem solved!

## Private class attributes through the `private` keyword
Finally, this one caught me by surprise when working with TS myself. I was never a big fan of classes being added to JavaScript myself, considering the Prototypal Inheritance model worked just fine and to be honest, classes are just syntactic sugar around it at this point.
However, when TS came into my life, I started seeing how they worked hard on actually implementing a more complete OOP model, so that sounded appealing and decided to give it a try.
But then I hit this wall and I was again, a bit pissed.

So what's the problem then? As it turns out, the `private` keyword, which you'd use to define a private property in your class, does not make said property that private.
_What?_ you ask.
Yes I know, let me explain.
This is how one would normally go about declaring a private property:

```typescript 

class Person {
  private name: string;
  private age: number;

  constructor(n: string, a: number) {
    this.name = n;
    this.age = a;
  }

  get Name():string {
    return this.name;
  }
  set Name(newName: string) {
    this.name = newName;
  }

  get Age():number {
    return this.age;
  }

  set Age(newAge: number) {
    this.age = newAge;
  }

}

let oMySelf = new Person("Fernando Doglio", 37);
oMySelf.Age = 38;
```

We're OK with this, right? We've declared both our properties as private, so we can fully control how and when they get updated. You can't really do `oMySelf.name="someone else"`, TS won't let you.

Of course, not until you do something like this:

```typescript 
for ( let prop in oMySelf) {
  console.log(Object.getOwnPropertyDescriptor(oMySelf, prop))
}
```

Or something like this one (this one is personally my favorite):

```typescript 
console.log((oMySelf as any).name)
```

Since the `private` keyword only works during compilation, the runtime won't be able to restrict you. So the `in` keyword can iterate over the actual list of private properties and through `Object.getOwnPropertyDescriptor` you can get the values and more information even, from those supposedly protected and secure variables. 
And don't get me started on casting the object to `any`,  you can do anything with the result of that!

Alright but that is just about _reading_ private data, which _could_ be a problem, but it's not as bad as modifying it.
Right? That's still protected, isn't it?
Nope, let's look at `Object.assign` for a second. And to test it properly, let's assign a private property that has no getter or setter to our `Person` class:

```typescript 
class Person {
  private name: string;
  private age: number;
  private secret: number;

  constructor(n: string, a: number, s: number) {
    this.name = n;
    this.age = a;
    this.secret = s;
  }
  ....
```

And how, let's create 2 objects of the same class with completely random `secret` values:

```typescript 
let oMySelf = new Person("Fernando Doglio", 37, Math.random());

let someoneElse = new Person("Other Person", 1000, Math.random());
```

We can see they're different through our `any` magic trick:

```typescript 
console.log((someoneElse as any).secret)
console.log((oMySelf as any).secret)
```

That should print 2 random floating-point numbers. However, if we add a single line before those `console.log`:

```typescript 
Object.assign(oMySelf, someoneElse)
console.log((someoneElse as any).secret)
console.log((oMySelf as any).secret)
```

The secret value is now the same. In fact, everything is the same between both objects. We've effectively overwritten  all the private values of `oMySelf` with the ones on `someoneElse`.

Can we prevent this?
Yes we can! We just need to use JavaScript's private property notation, instead of TypeScript's:

```typescript 

class Person {
  #name: string;
  #age: number;
  #secret: number;

  constructor(n: string, a: number, s: number) {
    this.#name = n;
    this.#age = a;
    this.#secret = s;
  }

  get Name():string {
    return this.#name;
  }
  set Name(newName: string) {
    this.#name = newName;
  }

  get Age():number {
    return this.#age;
  }

  set Age(newAge: number) {
    this.#age = newAge;
  }

}
```
It looks terrible, I know, but this is going to prevent every single one of our problems:

* `in` won't iterate over our private properties, so we can't really get their names.
* `Object.Assign` will ignore private properties, so they won't get overwritten.
* And trying to cast our object to `any` won't change a thing, since TS will complain that you're trying  to access a private property outside the class declaration.

All problems solved!

## Conclusion
TypeScript is fun and if used correctly, a very powerful tool that can help you avoid lots of mistakes! But it's not perfect and if you're not careful and you don't really understand both the language itself and JavaScript, then you'll run into potential headache-inducing problems or even worse: security issues that could cause catastrophic failure on your app.