- Published on
TypeScript: Top 5 common pitfalls developers face
Table of Contents
1. Strict Null Checks - strictNullChecks
Problem Description:
The strictNullChecks option in TypeScript tsconfig
ensures that variables are initialized to a non-null value, which helps prevent common runtime errors. If this option is not enabled, it can lead to unexpected behavior and errors.
Example: Only when we enable this option will raise an error that you have not make sure the departures
is not undefined before using it.
declare const targetCountry: string
const flights = [
{ from: 'LCA', to: 'ATH' },
{ from: 'AMD', to: 'VIE' },
]
const departures = flights.find((fl) => fl.from === targetCountry)
console.log(departures.to) //π« ERROR: Object is possibly 'undefined'.
Solution:
Enable strictNullChecks by setting the "strict" option in your tsconfig.json
file to true, or adding the --strictNullChecks
flag when compiling your TypeScript code. For example:
{
"compilerOptions": {
"strict": true,
"target": "es6",
"module": "commonjs",
"sourceMap": true
}
}
any
type
2. Misusing The any
type in TypeScript allows for dynamic typing, but can be misused and lead to runtime errors. If used excessively, it can also negate many of the benefits of using TypeScript.
Solution: Avoid using the any
type unless necessary, and use specific types whenever possible. If a variable's type cannot be determined at compile-time, consider using a union type instead of any.
Example:
// β
const handleAddOne = (a: any, b: any): any => {
return a + b
}
// β
const handleAddTwo = (a: number | string, b: number | string): number | string => {
if (typeof a === 'number' && typeof b === 'number') {
return a + b
} else {
return `${a}${b}`
}
}
interfaces
and types
3. Not understanding the difference between Interfaces and types are similar in TypeScript, but have different use cases. Using the wrong one can lead to unexpected behavior or errors.
Solution: Use interfaces
when defining the shape of an object or class, and types
for defining aliases for specific types.
Example:
// Interface
interface User {
firstName: string
lastName: string
role: string
}
// Type alias
type UserAlias = {
firstName: string
lastName: string
role: string
}
this
4. Using incorrectly the keyword The this
keyword in TypeScript can be used to refer to the current object, but can be misused if the context is not properly set. This can lead to runtime errors.
Solution:
Use arrow functions or bind the this
context explicitly to avoid errors related to this.
Example:
// β
class User {
private id: string
constructor(id: string) {
this.id = id
}
updateUser() {
setTimeout(function () {
console.log(this.id) //π« Potentially invalid reference access to a class field via 'this.' of a nested function
}, 1000)
}
}
// β
class User {
private id: string
constructor(id: string) {
this.id = id
}
updateUser() {
setTimeout(() => {
console.log(this.id)
}, 1000)
}
}
Note: Here in the first example this
refers to the anonymous function that's wrapped where in the second example the arrow function eliminates such mislead
generics
correctly
4. Not using Generics allow us to write reusable code in TypeScript, but if are not been used correctly, can lead to errors.
Solution: We can use generics to define reusable code that can work with multiple types. Type constraints should be defined for the generic type to ensure that the correct types are been used.
Example:
// β
class ShoppingCart<T> {
private items: T[]
constructor() {
this.items = []
}
public addItem(item: T) {
this.items.push(item)
}
public getItems(): T[] {
return this.items
}
}
const myCart = new ShoppingCart<string>()
myCart.addItem('item1')
myCart.addItem('item2')
myCart.addItem(22) //π« No ERROR is thrown and Type check was not present
console.log(myCart.getItems())
In this scenario, we have a generic class ShoppingCart that has a private array of items
of type <T>
. The class has two methods: addItem
which adds an item to the array, and getItems
which returns the array of items.
However, the addItem
method does not enforce that the item being added is of the same type as the items already in the array, which can lead to unexpected behavior.
To properly use generics, we need to add a constraint to ensure that the items being added to the array are of the same type as the items already in the array:
// β
class ShoppingCart<T> {
private items: T[]
constructor() {
this.items = []
}
public addItem(item: T) {
if (typeof item === typeof this.items[0] || this.items.length === 0) {
this.items.push(item)
} else {
throw new Error('Invalid item type')
}
}
public getItems(): T[] {
return this.items
}
}
const myCart = new ShoppingCart<string>()
myCart.addItem('item1')
myCart.addItem('item2')
myCart.addItem(22) //π« ERROR is thrown as expected
console.log(myCart.getItems())
In this second scenario, we're enforcing that the item being added to the array is of the same type as the items already in the array, or that the array is empty. If the item being added is of a different type than the items already in the array, we throw an error. Now, the code will correctly throw an error when we try to add a number to an array of strings.
Summary:
we have seen :
-
strictNullChecks
- Misusing
any
type -
interfaces
vstypes
- Keyword
this
-
generics