Errors 10 exercises
solution

Solving the Possibly Null or Undefined Error

The id we're getting from searchParams.get("id") could either be a string or null:


const searchParams = new URLSearchParams(window.location.search);
const id = searchParams.get("id");
// hovering over `id` shows:
const id: string | null;

If we try to call toUpperCase() on

Loading solution

Transcript

00:00 Okay, so the issue here is that ID could either be string or null, and if it's null here, if we call toUpperCase on it, we're actually going to get a runtime error. This is not so good, okay? If we get a runtime error, it's going to crash our entire application or we're going to hit

00:18 the closest try catch. It's not going to be good. And so this error from TypeScript is really bloody useful because ID might be null, and so if you try to call any methods on it or even access any properties on it, you're going to be screwed. So, how do we solve this? Well, we could, first of all, just do an optional chain on this.

00:38 What this does is it says ID, if it exists, then call toUpperCase on it. And you'll notice here, too, that if we go console.result equals this, then this result is either going to be string or undefined.

00:53 If ID is a string, it means that ID.toUpperCase is going to be called on it. But if it's null, then this optional chaining is going to trigger and it's going to return undefined instead. So we've sort of converted our null into an undefined, which is a little bit odd. But there we go. That's the behavior here. This is a perfectly reasonable way of doing it.

01:12 This will never fail at runtime, which is why TypeScript is not giving us any errors, but it also won't guarantee that this is a string that's being console logged. The second way to do this, this is more unsafe here. This is the unsafe way of doing it, is to use basically a bang operator here. I can't remember what this is called.

01:30 It's a non-nullable assertion. That's right. What we're doing here is we're saying ID, this actually doesn't do anything at runtime. This is a TypeScript-only assertion. And we say ID.toUpperCase. We're basically saying we are guaranteeing that ID is not null.

01:49 This is unsafe because if ID is null at runtime, this will cause an error. But if we happen to know that ID is always, always, always defined, sure, we can probably just get away with saying ID.toUpperCase. What I like to do in these situations, oddly enough, is you can actually add multiple non-null assertion operators.

02:08 So you can go ID.toUpperCase. And this gives you a warning, at least, that what you're doing is unsafe. But it's not great practice to do this, really, because ID actually is possibly null. It might genuinely not exist.

02:23 And so it's better to defensively handle that than just say, my ID is definitely not null. So this is sometimes useful if you really know more than TypeScript. But sometimes it's just not very useful and will cause more issues. So a better way to do this is to actually add some narrowing in here.

02:41 You can say, if ID exists, then you say console.log ID.toUpperCase. So now, this is clever. This is really clever. You notice that we haven't done anything to our ID.toUpperCase. We basically just said, if ID exists, or if it's truthy, then we get console.log ID.toUpperCase.

02:59 And inside the narrowing thing here, we get ID as a string instead of ID being possibly null. Really, really nice. We can do this as well with a typeof check, too. So we can say, if typeof ID equals string, then say console.log ID.toUpperCase.

03:15 And the final one we can do is we can actually say, if no ID, throw new error, ID not found. Amazing. And now, because it knows that we've basically just returned, or thrown an error in the situation

03:31 where it's null, ID.toUpperCase is always available here. It's a string. So there we go. Filtering out nulls and filtering out undefined, by the way, we get the same thing if ID was a string or null or undefined here. We'd have to handle undefined in basically the same way. Doing it this way means that you've got a few options, basically.

03:51 You can either do an unsafe call here, so an unsafe non-null assertion. You can do some optional chaining as well, which is a really nice terse way of doing it. Let me just console.log the results there. Or you can do some narrowing here. And the narrowing means if basically ID does not exist, we're not even going to console.log, which is quite nice too.

04:10 And so handling possibly null or undefined things is a really key feature of TypeScript and a key way that you're going to improve your code because null or undefined popping up in the wrong place, that is not going to be good for anyone because it's a recipe for runtime errors.