Getting More Useful TypeScript Errors
We have a large error message that was difficult to understand:
Argument of type '{ routes: { path: string; component: string | number; }[]; }' is not assignable to parameter of type '{ routes: { path: string; component: string; }[]; }'.
Types of property 'routes' are incompatible.
Transcript
00:00 Okay, let's first of all tackle this error, understand what's causing it, understand at least how to read this error at all. Okay, let's go line by line. Argument of type roots and all of this kind of crazy that you can see here that like, we've got a single quote there and a single quote here,
00:19 and that's basically saying the argument type that we're passing in is not assignable to parameter of type roots path component string this, okay. This is pretty unreadable already. Types of property roots are incompatible. So, okay, what it's saying is we're trying to pass in an argument.
00:37 So this is essentially the argument we're passing in, this root in config into a parameter, and the parameter is this config here, and these two are incompatible. And it's saying that the type of property roots is incompatible. So this property roots here, something inside roots has gone bad. We carry on.
00:57 Type path string component string or is not assignable to, okay, extremely unreadable, but there's an assignability error here. So it's basically saying that the root property, something is wrong inside there. We keep going further. Type path string component string or path string component number
01:16 is not assignable to type path string component string. Okay, getting a bit closer there. We can see that there's like a union type in the first one, and then actual sort of just object type in the second one. Type path string component number is not assignable to path string component string.
01:33 Types of property components are incompatible. So from this, we can assume that something has gone wrong with a component somewhere in this array. That's good. Types of property component are incompatible. Type number is not assignable to type string. Okay, so reading from this,
01:52 we can infer that type like, okay, something inside roots is bad. Something inside a component inside those roots are bad, and it's bad because it's type number instead of type string. How could we have gotten that information faster? Well, first of all, when I'm reading a big error like this,
02:10 usually it's because some deep, big object is not assignable to some other big, deep object. What I like to do is actually read it bottom to top, because here I can see that, okay, type number is not assignable to type string. What tends to happen in these situations is that because this error is like, I'll tell you why it's happening,
02:30 but it's basically building like a big legal case, right? Against your code. And it starts by saying, you were there on the night of the blah, blah, blah, blah, you were doing this, you were doing this. And then it finally sort of like goes all the way down
02:47 until it says, you were holding the murder weapon, right? Actually gives the most relevant piece of information right at the end when it's gonna like shock the jury the most. So we have here type number is not assignable to type string. That's the key difference here. There is somewhere a number being passed into a slot that expects a string.
03:07 So I tend to read that first. Then I'll tend to scan up and see what types of property are being incompatible here. So obviously component is the key property here. And then if I scan further up, I can see type of property roots are incompatible. So if I look up here, I know that there's something,
03:24 there's a number where a string is being expected. It's gonna be in a component inside the roots property. And this is really good for when you have massive conflict objects that are 600 lines long or something. But now I've told you how to decode this long error. We really need to understand how to make this error better
03:43 because this error is absolutely horrendous as it currently stands. How do we make it so that this line, this component 12 is the one that actually errors? Well, we have a few solutions here. The first solution is you can literally just pass the object in line here.
04:01 When you pass the object in line, you notice the difference here. Before we were declaring it as a separate object, this root and config. When you pass it in line, TypeScript is better able to make assumptions about what you're doing with that object. And you notice here that instead of having this huge legal case,
04:18 we actually just have the relevant piece of information on the relevant line. The difference here is because we were declaring it in a separate object, TypeScript might have thought, well, you're gonna use that root and config for a few different things here. It's not just gonna be passed into this one place. So instead of showing the error up on the root and config
04:37 where it might not be actually needed, let me show the error further down here. And so because the error is far away from the error site, it needs to give you this massive legal case to show why this error needs to occur. Whereas if it can give you all the contextual information
04:55 just by saying, it's right here, it's right here, then it only needs to give you the relevant information. So that's one way of doing it. The other way is let's say we took this root and config, put it in a type and we put it that type outside and we actually assigned that to the root and config here. So now root and config is of type root and config.
05:14 It's going to give us a better contextual error closer to the site. Type number is not assignable to type string. If we remove this, then we're gonna get our error further down here. But because our type is right up here showing us the way, we actually get the error in line. So this is a key way of doing it as well.
05:33 And a type annotation just at the right moment can save you a lot of pain. And there's one more here. Which one? Oh yeah, we're doing satisfies here. So satisfies is another way of specifying this, which is pretty useful. It means that our root and config
05:50 is actually gonna get sort of inferred as what it is. Satisfies tends to work really well with config objects. And it's also going to give our error in the correct place. So yeah, config objects in general, you need to use some sort of type annotation, whether that satisfies or whether that's a variable annotation like we see here.
06:10 And yeah, this is really, really key to understanding how to break down these big errors. Often these big errors are happening because the error site is far away from where it needs to be. So adding a type annotation just in the right place, if I can just even copy this over, just pull this up onto here, bam.
06:29 Now my error is exactly where I want it and it is a ton more readable.