Make TypeScript your ally

TypeScript, like that friend who always corrects your grammar, can be annoying or incredibly helpful. It's all about how you use it. I will demonstrate what effective TypeScript usage looks like, helping you make it your ally.

Types protect you from runtime errors

TypeScript helps you avoid most runtime errors, and especially the very common instances of TypeError. These errors occur when you try to access a property on an incorrect type, such as mistaking a number for a string, or vice versa.

TypeScript helps you catch these errors during compile time, which leads to fewer bugs for your end users.

In the example below, you can see how TypeScript prevents you from accessing number.toUpperCase() because that method only exists for strings.

const number: number = 15; console.log(number.toUpperCase()); // ❌ Property 'toUpperCase' does not exist on type 'number'. const string: string = "Hello"; console.log(string.toUpperCase()); // ✅ We're okay here, because the `toUpperCase` method exists on strings.

Never Play the Guessing Game with Parameters Again

When using plain JavaScript, you will find yourself frequently looking at a function definition to see what sort of values you need to be passing.

Looking at the code below, it's unclear whether myDate should be a string or a Date object. You can only find out if you check the function implementation and see how it uses the inputs.

const myDate = "2023-01-01"; daysFromNow(myDate); // ❓ will this work? daysFromNow(new Date(myDate)); // ❓ or maybe this?

In TypeScript, you define the types of input you expect, and your code editor will let you know what sort of parameters to pass. This helps you avoid runtime errors, and it makes you more productive by surfacing useful information while you're coding.

const daysFromNow = (date: Date) => { // ... } const myDate = "2023-01-01"; daysFromNow(myDate); // ❌ Argument of type 'string' is not assignable to parameter of type 'Date' daysFromNow(new Date(myDate)); // ✅ No complaints here!

As you can see in the screenshot below, your Code Editor should include a little hint as to what input the function expects.

Screenshot or image included in article

Future-proof your code with TypeScript

TypeScript helps you set up guardrails, so when something changes in the future, it's like having a friendly nudge pointing you where you need to tweak things.

Here's a relevant example.

// Let's say we just added a new UserRole: 'guest' type UserRole = 'admin' | 'user' | 'guest'; const messageForUserRole: Record<string, string> = { admin: "Hello admin", user: "Welcome back" }; // TypeScript is not complaining... even if we forgot to add a message for guests // How can we utilise TypeScript to future-proof our code? const messageByRole: Record<UserRole, string> = { admin: "Hello admin", user: "Welcome back" }; // Property 'guest' is missing in type '{ admin: string; user: string; }' // but required in type 'Record<UserRole, string>'.

In the example above, you can see how using types forces Future You or your colleagues to think about adding rules, switch cases or missing properties whenever a type is amended.

TypeScript can help you effectively safeguard from bugs when changing an object's properties has side effects that appear elsewhere.

Protect from backwards incompatible changes

It's easy to run into bugs when your backend has changes that your frontend has yet to reflect. For example, when you remove a field from an API call but your frontend is still trying to access that field, you could run into errors.

A great approach with TypeScript is to auto-generate types for your API responses. This method flags any incompatible changes between your frontend and backend.

Copyright ©2024 Bestpractices.tech. All rights reserved.