Common Typescript Mistakes You Don’t Know About

In this post, I'll highlight common TypeScript mistakes found in projects. Many developers switch from JavaScript to TypeScript without learning TypeScript's fundamentals. They think the code is similar, but end up not leveraging TypeScript's benefits. Instead, they resort to hacks to solve problems.

Disabling Strictness

One common mistake is disabling strict rules in tsconfig.json.

{
  ...
  "strict": true
  ...
}

The strict option enables various checks and combines multiple strict rules.

Setting strict to false makes TypeScript less safe and is not recommended.

Less strict TypeScript leads to fewer errors during compilation and more errors at runtime, which is not a safe approach. It's recommended to never disable TypeScript rules in tsconfig.json, as it won't make your code safer.

Operator any

Another common mistake arises from a lack of familiarity with TypeScript, leading developers to resort to using the any type as a quick fix.

interface User {
  id: string
  name: string
}

const user: User = {}

In this example, we attempted to define a user object, but we didn't specify the correct fields.

any

When faced with errors they don't understand, people often resort to using the any type to resolve their issues without fully understanding the underlying problem.

const user: any = {}

Using any type essentially bypasses all TypeScript checks, converting TypeScript code into JavaScript code without any type safety. While this might seem like a quick fix, it introduces the risk of runtime errors that could have been caught during compilation.

If the any type is used more than 20 times in a project, it indicates a significant reliance on unsafe typing practices. This can lead to decreased code quality, maintainability issues, and an increased likelihood of runtime errors.

The any type should be used sparingly and only in exceptional cases where the type of a value cannot be known or inferred at compile time. Overuse of the any type can undermine the benefits of TypeScript's static type checking and lead to less predictable and more error-prone code.

Unknown operator

The unknown type is a safer alternative to any in TypeScript. While any effectively disables type checking, unknown provides a way to represent values of an unknown type while still enforcing type safety when accessing or manipulating those values. It encourages developers to handle type checks explicitly, leading to more robust and maintainable code.

const getFoo = (something: any) => {
  console.log(something.at(1))
}

This code won't show any errors because it's using the any type. So, even if something isn't a string, the at function can still be called on it without a problem. This isn't good and can cause unexpected issues when the code runs.

const getFoo = (something: unknown) => {
  console.log(something.at(1))
}

With unknown, this code behaves differently. It will throw an error because you can't call the at function on an unknown variable. This is beneficial because it catches potential issues before they happen.

unknown

It means that we can't directly use any function call on unknown before ensuring that it's safe to do so.

The main difference is that unknown alerts you to potential issues, whereas any does not.

What you need to do is narrow your type before applying any operations or parameters.

Narrowing Type

The next point is that many people are unfamiliar with type narrowing and don't know how to do it properly. Type narrowing, such as using unknown, helps refine our types to make them more specific.

const getFoo = (something: unknown) => {
  if (typeof someting === 'string') {
    console.log(something.at(1))
  }
}

In this case, we narrowed our type by checking if we received a string. This code won't produce any errors because TypeScript is confident that the data type is correct. So, using unknown allows us to indicate to TypeScript that we're uncertain about the actual data type, while type narrowing enables us to refine that unknown data without explicitly converting it to a known type.

You can employ type narrowing in various scenarios, such as when narrowing a union type. Remember this useful technique as it can be quite handy in refining your types as needed.

Type Assertion

Another common issue is the overuse of the type assertion operator as.

const getFoo = (something: unknown) => {
  const str = something as string
  console.log(something.at(1))
}

Instead of narrowing types properly, we used as to forcefully treat our data as a string. But this is like using any – it's not safe. We're telling TypeScript to treat the data as a string, even if it's not, which can lead to runtime errors.

This code isn't safe, even if TypeScript doesn't show any errors during compilation.

Similarly to any, you should use the as operator as sparingly as possible.

Don't Skip Types

Another important point is that people don't specify types frequently enough. While TypeScript can infer the correct data types in some cases, it's not always accurate. By explicitly defining types, we ensure that our code behaves as expected and remains resilient to changes in the future.

guess

Even in the code we just wrote, TypeScript inferred that our function returns void. However, in good coding practice, we should explicitly specify the return type.

const getFoo = (something: unknown): void => {
  ...
}

In such cases, TypeScript will ensure that the returned type is accurate. If we omit the type, TypeScript won't detect an error if we inadvertently return a different data type.

That's why it's highly advisable to explicitly specify the type for each variable to ensure the correctness of data types throughout the code.

Understading Errors

Understanding TypeScript errors is crucial for writing robust code. Instead of resorting to quick fixes like type assertions or using the any operator, developers should take the time to learn TypeScript thoroughly and understand the errors it produces. This will lead to better code quality and fewer runtime errors.

understand

Understanding the error messages is crucial for fixing issues effectively. In this case, the error is indicating that the function is expected to return a value of type number, but it's returning void instead. This suggests that the function is not returning anything when it should be returning a number. To fix this, you need to ensure that the function returns a value of type number.

Take your time, especially if you're a beginner, to correctly address TypeScript errors.

Happy Path

Another crucial point that many people overlook is that TypeScript is a static analyzer of your code. It doesn't have a complete understanding of your entire project or the specific values it contains.

const getData = (data: string): void => {}

const data: string | undefined = undefined
getData(data)

It might be that in your application, you are certain that a value is never undefined. However, TypeScript doesn't consider the specific values in your application; it only cares about the validity of data types. If a function parameter can potentially be undefined based on its type declaration, TypeScript will throw an error if you attempt to call the function without handling the possibility of undefined.

We're attempting to provide a data type that isn't valid for the argument.

The solution here is to either check outside for the data type or allow your function to accept undefined and handle it internally.

Using Optional Too Much

Another common issue is that people use optional types excessively.

interface User {
  id: string
  name?: string
  age?: number
  isActive?: boolean
}

This code is technically correct, but it's not ideal. The problem lies in the excessive use of optional properties. Essentially, this interface resembles any, as every field can be undefined. Consequently, the type validation provided by this interface is insufficient.

Consider a scenario where you have users with only an id and no other fields. When using this user object in a function, it may lack the necessary fields. It's advisable to mark most, if not all, properties as required and limit the use of optional properties.

Union vs Enum

Another common issue is the excessive use of type unions instead of enums.

type State = 'active' | 'completed' | 'rejected'
const state: State = 'active'

This code is valid and functional, but it often becomes problematic when you need to work with specific values instead of just data types.

if (state === 'active') {
  console.log('we are active')
}

Since types only exist in TypeScript and not in JavaScript, we can't use a type as a value. However, we can rewrite this code using enums instead.

enum State {
 active = 'active'
 completed = 'completed'
 rejected = 'rejected'
}
const state: State = State.active

We wrote the same code but used enums instead. Already, we used State not only as a data type but also as a value when we assigned the string to it. This is one of the benefits of enums. Assigning plain strings is not valid. We can only assign values from the Enum, which is incredibly safe and allows us to reuse the code.

if (state === State.active) {
  console.log('we are active')
}

Secondly, we can use enums inside conditions, which is really handy because enums are transpiled to JavaScript and exist there as values.

Exclamation mark

One more common Typescript mistake that people make is using an exclamation mark too often.

const getData = (data: string): void => {}

const data: string | undefined = undefined
getData(data!)

I used the same code as before, but to fix the issue of providing undefined, I used an exclamation mark.

The exclamation mark tells TypeScript that we are certain the value exists.

Realistically, you can never be absolutely sure that the value is present, making this code extremely risky. It's on par with type assertion and using any.

const data: string | undefined = undefined

if (data) {
  getData(data)
}

The correct solution here would be to check if the data property exists before passing it to the function.

Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!

Did you like my post? Share it with friends!
Don't miss a thing!
Follow me on Youtube, Twitter or Instagram.
Oleksandr Kocherhin
Oleksandr Kocherhin is a full-stack developer with a passion for learning and sharing knowledge on Monsterlessons Academy and on his YouTube channel. With around 15 years of programming experience and nearly 9 years of teaching, he has a deep understanding of both disciplines. He believes in learning by doing, a philosophy that is reflected in every course he teaches. He loves exploring new web and mobile technologies, and his courses are designed to give students an edge in the fast-moving tech industry.