Adventures In TypeScript: Typing My Way Out of Problems

I've trying to bang out tasks at work, so I wrote about one I did this week.

Table Of Contents

Introduction

I’ve been taking more and more tickets with the new gig. This week I was tracking down some text to make some updates, and this was not as simple as I thought it might be, so I decided to take some notes and reflect on some stuff I learned.

The Problem

The task was simple, update some text on the platform. I quickly discovered that it was not as straightforward as I thought.

The first issue was trying to track down where some of these values which needed changing lived.

The second problem was the updated content included a link to some external docs for our platform.

So I found where the objects storing the text were. That problem is solved. On to the next

So how do I add a link to these strings I mentioned? It seemed easy; you replace the string, but how do you make the link element work?

The solution should have been more obvious to me at that point. But sometimes I forget stuff; I’m human. I’m allowed. So let’s go through my process.

I thought I could just put an href tag into the string value I was updating. Like so.

First Thought Link String

And I pass it into the JSX like this.

First Thought Link String in JSX

That won’t work since you end up with this.

Link in JSX

Since JSX will interpolate this as a string or a JavaScript function and won’t interpret pure HTML elements as HTML tags, this isn’t my solution.

So we needed something closer to this.

Link Component in JSX

I’m using the prebuilt Link component provided by Material UI, and I needed to get the link passed into this component in our JSX as a string, which solves this problem and takes us to the next problem.

Objects and Typing Issues

Now I have a new goal.

I created an object literal to hold my values and possibly use it to hold URLs for additional help docs in the future.

DocMap literal

The next step was to pass that URL around. Here’s another layer; I wanted to reuse a string value used to grab all the config options to cut down on hard-coded stuff. So I made the key in my object literal match possible values for this string object. Something like this.

Variable as Key

We’re using bracket notation to pass in the object key and have it evaluated as the variable key rather than a standard dot notation, which would throw an error.

Although you undoubtedly noticed the squiggly line yelling at me, that gave me this message that I didn’t understand.

String TypeScript error

What I initially did confused TypeScript, and thus the error vomit. If we look at the error, though, it’s less error vomit and more a breadcrumb trail back to where I made a mistake.

Our string variable key is typed as a string, but TypeScript has no idea what the context is since we’re using an object literal without a type. So for TypeScript, key could be anything in my code, and now I’ve confused it. Here’s where I stepped on a landmine.

Cheating

My first solution was to cast my object as the any type.

DocMap as any type

This cleared the error, and I could run the project with the behavior I wanted. Problem solved, correct? Nah.

TypeScript introduced type checking into JavaScript, effectively making it a statically typed language. So the types of objects are known at compile time. This helps create type-safe code and catch problems earlier. The issue with my solution is that it skips the good parts of TypeScript by turning off type checking.

Why use the language if we’re not going to use the language? I looked for a better solution.

Another Solution

A not great solution would be to use some magic.

Type Assertion solution

A bit is going on here. We’re still using the bracket notation, but in the brackets, we’re casting the variable as type string using the as operator; this is also called a type assertion. And then, we’re utilizing the keyof operator in TypeScript to create a union type for our DocMap object, which is passed in using the typeof operator. It’s a solution that confuses me to think about, makes the code harder to read, and introduces a code smell since it seems like a hack rather than a planned solution.

The Better Solution

A better way might be to use mapped types and interfaces. As a general rule in my TypeScript adventures, I’ve been sticking with making the correct types or interfaces for objects. So following that, I can do something like this.

Mapped Types Solution

This solution creates an interface for the help doc objects, and then I can create a new object with our data using that type. I tested making a mapped type for the first property as a demonstration. Generally, you use those when making types with properties that might not be known ahead of time. It’s a generic type that we can use to denote the types for the value and keys in any DocMap type object.

My only issue is that it’s still a bit roundabout for what I want. This is one-time use, and it’s not being exported anywhere. So I want something even simpler.

My Solution

My solution was to use a Record type to create an object type with the required property types.

My solution using the Record Type

The Record type is a utility type that uses the passed in types, via the angle brackets <>, to map the types for the object property names and values. This is type-safe, easy to implement, and checked all the boxes I needed.

Conclusion

This is the strategy for learning the code base. Spend my energy tackling as many problems as I can early. We called this “racking up the cash register” when I was wrestling. The more I take on, the more I learn, and the more I learn, the bigger the challenge I can take on. I hope this helps someone in their learning journey!

-George