Typescript Pitfalls
Tags: typescript, webdev, angular, react
Published: 2020-03-28 22:02:06

---
title: Typescript Pitfalls
published: true
description: Typescript Pitfalls
tags: typescript, webdev, angular, react
---

Here is ~~the~~ my list of cases where the Typescript type system may resolve in *unexpected* results. They're mostly well documented on [Github](https://github.com/Microsoft/TypeScript/issues), but I often find bugs regarding the following collection.

# Conditional Spread Operator in Object Literal with Optional props
[TS playground](http://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgWQE98IATYAGzgG8AoOOJTAW2AGcAuONmKZAcwDaAXQDc9OJn7AuSAK7MARmnENMpUlHZsA-Fx58k-WgF9atbBCQ84zYmUoBGLkRLkqAXhoSmrTnEEAckxAgBo4QMUwiOxA4VCJKRk4AGYAJgS1TW0uNJSAFjz8uAB6ErgYQjA8AAs0PGxMOX4a+EVCOAAVAGVTc0treDs3Shd7dzgvOgZfdi4gkPDI6MDY+MTpLnTMuAA6fYAKXjk8HRpJbLZ-IqK4EzguahMASnCyiqqIODqtOAA5ADynTgjWarTg7S6vTMQA
)

```typescript
export interface MyModel {
  names: string[];
  age: number;
  address?: string
}

const myModel1: MyModel = {
  names: ['a', 'b', 'c'],
  age: 32,
  adress: 234234 // type here caught by TS since "excess property checking" is done
}

const myModel2: MyModel = {
  names: ['a', 'b', 'c'],
  age: 32,
  ...(true ? { adress: 234234 } : {}), // typo here NOT caught by TS, even if it's an Object literal
}
```
# Array.prototype.map() of any[]
[TS Playground](http://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgLIE8CS5rycgbwChlTlgATALmRAFcBbAI2gG4SyQ4GIaBnMFFABzdgF8iCAPYgByBugCCUKHHQ04IdMgC8yANoAmADTIAzKYDkACwgAbO1MunBdCAF12kmXIXZIsIgQfACMNBj+uEH67rrySipqAHQMcAAOABQZAJS6AHzIGQTIAO5QMsI0lmnlaZbIYtnZrMgA9K3IMKAoMFJQyAAqAMo0JdZwYMgA-ACERN6yk344gUh8huFYK3gQMXEKyqroLe2d3Z19gyOl45OzREA)

```typescript
interface MyInterface {
    id: number;
    name: string;
}
const myArray: any = [2, 3, 'hello', true];

const myInterfaces1: MyInterface[] = myArray.map(() => ({ wrong: 'prop' })); // fine for TS: what ?!

const myInterfaces2: MyInterface[] = myArray; // fine for TS: what ?!
```

# Array.fill
[TS Playground](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgMIQDYeQbwFDLIDOwA5iAFzIDkAGtcgD40Dy1A3AciBEZACZV8hQgAcoAe1FU+UUKS4BfPMrwIJIPsTBxIVdFgDaAXWQBeZAEEoUOAE8AFADYAlADoYwLA4AMLzmoaRBIYEG4YEqQOfLoQhj7GbiTkLsgA9GnIAK4g-BCePPyBmiFhEVExkPGJPHwQ-G7iUqkZyAhwICASYMhQEHD8yE2i0GB2NMMMEjDZufmg9UA)

```typescript
interface Cell {
  sign: 'X' | 'O';
  nested: {
    prop: string
  }
}

const state: Cell[] = Array(6).fill(0);

console.log(state[0].sign) // undefined
console.log(state[0].nested.prop) // cannot read property 'prop' of undefined
```


# Typo on optional properties
[TS playground](https://www.typescriptlang.org/play/index.html#code/JYOwLgpgTgZghgYwgAgOoHsoBsAmyDeAsAFDJnIIBcyARuulhHCANwnnJw45QQDOfAPzUQAVwC2NaG2IBfEiQToQfMMgDumXNQzY8AXgLtyVZGCiiIAGmNkuvAdQBMAZgAsyAPSewATwAO6MgAFtAoCHCiAObBajS+yAAqAMok8sSKyqoaWjh8OrkA2gC6yIaFAIxWyE7VLsUAdOJw-gAUwWUAfMit+KbmltX2-PnILrIAlBNenmYBKKG81TSiajCgKDCYScnU6sFwYIJAA)
```typescript
interface World {
    c: boolean;
    address?: number;
}

const world: World = {
    c: true,
    adress: 234 //typo here caught by TS since "excess property checking" is done 
}

const worlds: World[] = [1, 2, 3].map(h => ({c: true, adress: 3})) // type here, but fine for TS: what ?!
```

# Typescript types are "open", not "sealed" or "precise"
[TS playground](http://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgArQM4HsTIN4BQyyMwUGYAXMhVKAOYA0RyANnBdbQ88QLbAAJoNYQA-FzB0Q9AgF8CBGAFcQCMMBw04ANwjpyOABQAHTDmoHsIAJT4WUCGGVRcIZa1YBueYoQ4KZGgoLChkAF57YlJyKmQAcgBlSHgQeN42Djj4gCE4ZT56OCgwEGh0lgERCGp4jkEYeOQAembkMABPEyxkAAtoCF8CDF19cxAjYNCbLxa2qbD-cDhQDGQ4dq6sRmQAI2UwZGAweLXSMpJQ5AAVRKA)

```typescript
interface Person {
  first: string,
  last: string,
  middle?: string
}

function savePerson(person: Person) {
  return null;
}

const error = {
  first: 'Stefan',
  last: 'Baumgartner',
  midle: 'asdf' // typo here
}

savePerson(error); // error contains a typo, but it's fine for TS
```

> As you write functions, it’s easy to imagine that they will be called with arguments having the properties you’ve declared and no others. This is known as a 'sealed' or 'precise' type, and it cannot be expressed in TypeScript’s type system. Like it or not, your types are open.
Understand that JavaScript is duck typed and TypeScript uses structural typing to model this: values assignable to your interfaces might have properties beyond those explicitly listed in your type declarations. Types are not 'sealed'.
― Effective Typescript, Dan Vanderkam

# Union Type
[TS Playground](
http://www.typescriptlang.org/play/#code/JYOwLgpgTgZghgYwgAgELCgE2QbwFDLIwA2AngMoAOEEmAXMiAK4C2ARtANx4C+eeoSLEQoAYsADOAC1wFCyCQHdgLKjXqNWHKL34QAHpQD2UMMjClqaDJkkyAvNazIAPsnHTueBEZASzbDZ2DOhYdsiO+IQkFNS0DADMADRySipq8cgArLp4APR55lJwYADkEsiKUqRFEDUIcMTEtMjAZgBEAKogwL7tyAAqlhBAA)
Union type is not a [Discriminated Union Type](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions)
The OR (|) symbol may be misunderstood.

```typescript
interface Bird {
  flySpeed: number;
}

interface Fish {
  swimSpeed: number
}

export type Birdish = Bird | Fish;

const birdish: Birdish = {
  flySpeed: 3,
  swimSpeed: 5
}

// that's why they called it "Union" Type
```

# Keyof of union type is never
[TS Playground](http://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgArQM4HsTIN4BQyyIcAthAFzIZhSgDmA3AQL4EGiSyIoAywGBAwAHOLkLEARsChgAFtQAicSC2IATCKvkB+Zaogt2YAJ4iU6KNhABBEBoDKY3AF40mHMgBkyAUNFxFjMLD2scAHkoZ3FkdysbZAAfP0FhFxYCEMtPOwcYkABpCFMMOOQAaxKsGGQACgSceycXAEomYgB6TuQAclIKABo+mTl5XuHerR1erPMc8JAoguLS8qrTGvrGpei2juRu5AAVeeRgMpAIADdoagB3eVVkXQBCAiA)

```typescript
interface Person {
  name: string;
}

interface Lifespan {
  birth: Date;
  death?: Date;
}
type PersonAndSpan = Person & Lifespan;
type PersonOrSpan = Person | Lifespan;

type PersonAndSpanKeys = keyof PersonAndSpan;  // 'name, 'birth', 'death'
type PersonOrSpanKeys = keyof PersonOrSpan;  // Type is never: what ?!
```
[Here is the solution](https://stackoverflow.com/questions/49401866/all-possible-keys-of-an-union-type)
type KeysOfUnion<T> = T extends any ? keyof T: never;
type AvailableKeys = KeysOfUnion<Batz>; 

# Spread operator of a typescript class
[TS Playground](https://www.typescriptlang.org/play?#code/MYGwhgzhAEBiD29oG8BQ0PWPAdhALgE4Cuw+8hAFAA7EBGIAlsNGAFzQ7EC2dApoQA00Wg2bQ6HAoUY4A5gEoU6TAF9UKjNz74AFvAAmlJWkzR16jQDNiOMo1zQ+BxvkpXEHBPBOpL2PHxoD3gvRGgAXk4+AHc4REoAJmEAchCUhQBuVACCYMQAZWpCPjADSJQAOmqQy3wAT2o+aABrPnr4K28Kto6raAamzvz4TOgAenHoACIwaegAHxm6eaXp7T1DadRB5t7O7yKSsp724d3hkKPSgzHJmbnF5e3UZ1d3RCzXlzcr4pushMpgBRABKoIA8qCOAAFQjwJqEBrQFIbfQGFLQRgwbjYiCyOTQIA)
```typescript
class Foo {
    constructor(public a: number, public b: string) {
    }

    method() {
    }
}


function edit(foo: Foo) {
}

const foo: Foo = new Foo(2, 'foo');
const fooSpread = {...foo}

type keyofFoo = keyof typeof foo; // "a" | "b" | "method"
type keyofFooSpread = keyof typeof fooSpread; // "a" | "b"

edit(foo);
edit(fooSpread); // ERROR: Property 'method' is missing 
```
That's because Object spread only copies enumerable own properties...

# Array.find over a (discriminated) union type
[TS Playground](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgILIN4Chm+XALmRAFcBbAI2gG4sBfLLUSWRFAIUxzwqIGcwUUAHNaDLGACeABxQBZYAA8IAEwAqMlAF5kACnQAfZOwCUAbQC6jBAHsQA5GSWqNsoguXrNyHWex58IgBmABp6MP8A3mQAcgEhEGEY+gtaLFt7MGQ7ABtJVCJUSx9HZy9ZADoYYByWXTIfAD5YuBjkUEcTaiA)
```typescript
interface A {
    a: number;
}

interface B {
    b: string;
}

type MixedType = (A | B)[]

const mixedType: MixedType = [{
    a: 3,
},
{
     b: 'string'
}];

const onlyA: A[] = mixedType.filter(m => 'a' in m); // error, Type '(A | B)[]' is not assignable to type 'A[]'.
```
Here is another example:

[TS Playground]( 
https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgELCgE2QbwFDKHIwA2AngBQCUAXMgG4D2wmA3AUSXGQKIDmfAM7U6TFuwC+ePKEixEKAGLBBAC1wdCggO7AAtiIbM2m5F14DhtI+LxS8YMgAcUAQRD64JZAF40GbAAfZGU1djwYAFcQBDBgRhBkFVDVChcwOhTkYPQsa3SkwRCVdXwiZCgIMEioRLSq5DgilKoAOh19ZABCHz9ozAgYUAgTewQEwTBGjz0vQTp3TxIAbQBdX2RlsqJSMjpqXwA+XGQJABpTc34hfaojk6lzjXKOvVv7nFOL8qvLd59jp9HqtpMAYMgKMkShQ4DM5ssAAyrKh3baEcYgSbEEqZEobWFLQSI1asZAAejJ2LUhWQjhc2CaxTUZ2QACNImBWly7NIMVihmpcdS-ATZiRBK0hiBMDD7lC1DCUaSKchoFBGFAWWBVChKoJIiQwEVGOCpdgVP4sNkmepgv1BsNsHggA)
```typescript
interface Bird {
    fly(): void;
    layEggs(): void;
}

interface Fish {
    swim(): void;
    layEggs(): void;
}

type Animal = Bird | Fish;

function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

const animals: Animal[] = [{
    fly: () => { },
    layEggs: () => { }
}, {
    swim: () => { },
    layEggs: () => { }
}]

if (isFish(animals[0])) {
    const fish: Fish = animals[0]; // fish is typed as Fish, but...
}

const fish: Fish = animals.find(a => isFish(a)); // error, the results of find is Bird | Fish | undefined 
```

# Class implements interface
[TS Playground](https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgVQGc04BvAKDjjCgjAC45CYpkBzOAHzgCMIIANsExIA3OQC+5cqEiw42AZkKECxKAGElKxAFswQ3cBSqiJClRp04AXjgByK2HvSq2CEmZQArthjQACgBKMkoqOBgACwRCADonWwifYHEqKUkgA)
```typescript
export interface User {
  prop: string | boolean;
}

export class UserClass implements User {
  prop = 'prop'

  constructor() {
    this.prop = true; // Type 'boolean' is not assignable to type 'string'.(2322)
  }

}
```

That's all so far, feel free to write me what you think! :)