DEV Community

Ed Bentley
Ed Bentley

Posted on

TypeScript Gotchas

TypeScript can be a great language to work with. If you're starting a new mid to large project in JavaScript, I would certainly recommend using TypeScript at the moment. Knowing your code is correctly typed brings a lot of confidence when it comes to making changes.

However in order to get this great experience, I always have a few "gotchas" in the back of my head while writing TypeScript. These are things to watch out for, which can cause runtime issues if you're not careful.

This can provide us with a "sound" codebase, even though TypeScript is inherintly an "unsound" type system.

These examples have been tested with TypeScript v3.8.3

1. Beware JSON.parse and .json()

const obj = JSON.parse('{ "x": 5 }');

obj.hello(); // uh-oh! obj.hello is not a function
Enter fullscreen mode Exit fullscreen mode
const data = await fetch("https://pokeapi.co/api/v2/pokemon/1/").then(res => res.json());

data.hello(); // same again!
Enter fullscreen mode Exit fullscreen mode

Here obj and data are returning the type any, even in strict mode, and so TypeScript doesn't warn you about these runtime errors.

Solution: you should decode anything that you don't know the type of. I recommend bringing in a library like io-ts for the job. It's also really useful for query parameters.

2. Beware record types

type MyObj = Record<string, string>;

const myObj: MyObj = {};

const result = myObj.hello;
Enter fullscreen mode Exit fullscreen mode

What's the type of result? TypeScript tells me it's string, but actually it's undefined!

I still find Record to be useful in some cases but you really need to be careful with it. You could also consider using something like fp-ts lookup in order to be extra careful.

3. Beware type assertions

type MyObj = { hello: () => void };

const myObj = {} as MyObj;

myObj.hello(); // uh-oh! myObj.hello is not a function
Enter fullscreen mode Exit fullscreen mode

Just avoid as and angle bracket type assertions as much as you can.

4. Beware catch

try {
    throw undefined;
}
catch (e) {
    e.hello(); // uh-oh! Cannot read property 'hello' of undefined
}
Enter fullscreen mode Exit fullscreen mode

Here e has type any so no type errors are reported. Interestingly if you try to annotate e like

catch (e: Error) {
Enter fullscreen mode Exit fullscreen mode

TypeScript won't let you do that which is good. But without it it's still returned as any. 🀷

Watch out for .catch on a Promise too.

5. Don't use any or !

By now this should go without saying. Using any breaks down the point of bringing in TypeScript and you loose all of your confidence everything is sound and runtime error free.

! can be very convenient but it's best avoided. My only exception is in test code.

Avoid things like this

function lowercaseOptional(val?: string) {
    return val!.toLocaleLowerCase();
}
Enter fullscreen mode Exit fullscreen mode

and prefer explicitly handling the undefined case like this

function lowercaseOptional(val?: string) {
    if (!val) return "";
    return val.toLocaleLowerCase();
}
Enter fullscreen mode Exit fullscreen mode

You can ensure any and ! aren't used by setting up the eslint rules no-explicit-any and no-non-null-assertion.

6. Be careful with libraries!

There are some libraries which served their purpose brilliantly in JavaScript. But in TypeScript, it's simply not feasible to keep using the same API while also keeping your code sound.

This example is taken straight from the lodash doc and converted to TypeScript:

function square(n: number) {
  return n * n;
}

var addSquare = _.flow([_.add, square]);
addSquare(1, 2);
Enter fullscreen mode Exit fullscreen mode

What's the return type of addSquare? any ! 😩

Honourable mention - arrays

const array: string[] = [];
const val = array[1];
Enter fullscreen mode Exit fullscreen mode

TypeScript tells us val is of type string, when really it's undefined. However this is a gotcha in most programming languages, and it really wouldn't be practical to check for optionals in something like a for loop. But it's good to be aware of.

Do you have any more "gotchas" I'm missing? Please share in the comments below!

Top comments (6)

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt • Edited
  • A gotcha I met recently is Browserified TypeScript's private fields aren't private.
    • It might be better to prefix with underscore, just like Python.
  • On publishing a library, source code isn't bundled by default. Have to declaration: true and declarationMap: true in tsconfig.json.

Solution: you should decode anything that you don't know the type of. I recommend bringing in a library like io-ts for the job.

I use runtypes, or just ajv. Is there a benefit to switching to io-ts? Also, learn fp-ts?

Collapse
 
edbentley profile image
Ed Bentley

Those look like great options too, though I haven't tried them myself. The io-ts API has worked well for me so I've stuck to it. Whether to use fp-ts or not would need another blog post to discuss! Generally I don't let the fp-ts types like Either propagate to the rest of my code, but it's really up to whatever you prefer.

Collapse
 
adam_cyclones profile image
Adam Crockett πŸŒ€

In the first example, it just looks like types need to be declared, turning on strict mode would help as well.

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt

Yes, I am talking from the perspective of Browser's JavaScript console; and non-Node.js developers.

Thread Thread
 
adam_cyclones profile image
Adam Crockett πŸŒ€

Sorry I commented in the wrong place haha.

Collapse
 
isaachagoel profile image
Isaac Hagoel

Thanks for the write-up. Good examples. Coming from languages like C++ and Java Typescript always gives me a strange mix of emotions. On the one hand it is comforting and helpful. On the other hand it sometimes feels super tacked on and full of holes.