Unions and Narrowing 28 exercises
solution

Handling Optional Parameters in Discriminated Unions

Let's explore how to handle the cases where no specific shape is passed into the calculateArea function.

Attempt 1: Create an OptionalCircle Type

One possible first step is to create an OptionalCircle type by discarding the kind property:

type OptionalCircle = {
	radius:
Loading solution

Transcript

00:00 Okay then, so the most sensible thing we can start off with here is thinking about the kind of third state that we want our setup to be in. We have three kind of like inputs you can possibly add here. We have the one where you explicitly pass a kind circle and

00:16 you get radius 5. Then we have one where you explicitly pass a kind square and you have to pass in side length. And you need to have one where you don't pass in one, so like an optional version. So what would that look like? We might have like an optional circle

00:33 in here. And the optional circle would not specify a kind and then you get radius number on here. So we could add this into here, so it would be circle or optional circle or square. Now this works from the outside of the function here, so we now figure out that okay you can

00:51 pass in radius here and it should return properly. All the test cases will pass, but inside here it's actually going to, well actually first of all it's going to error in a couple of ways. It's going to error first of all because property kind now does not access, like it's not accessible on the type of shape. There's one of these members here which no longer

01:11 has a kind on it, which means it's no longer actually a discriminated union. Okay, that's not particularly great and that means that it breaks all of the logic inside here. So this doesn't feel like the right solution. So I'm actually going to delete this and show

01:29 you a slightly different version. Let's keep it around just for clarity, but let's remove it from our union. So optional circle, a kind of failed experiment. The other way we could do this is we could extend the type of circle to be something that doesn't require a kind.

01:45 We can do that by actually adding in a little optional thing in there. So now type circle doesn't require the kind in here or you can pass in kind as undefined. And so if we look at the bottom here, you can see now that we get exactly the same behaviour. This is now

02:02 working from the outside. We can pass in kind, so we can say it's a square and if we pass that in, then it's going to require a side length and radius won't be allowed to be passed. We can pass in undefined, which is nice. Now again, this defaults to a circle and we get

02:17 back kind circle here. Okay, nice. That's good. So that seems to be working from the outside of the function. But how about the inside? Well, if we look inside here, we can say if shape.kind equals circle, okay, this now means that this is guaranteed to be a

02:33 circle inside here. That's nice. But now shape or rather down the bottom when we check the side length, this is no longer being narrowed. This is because we're not checking to see if shape.kind is undefined in here. We could say if shape.kind equals circle or not shape.kind,

02:52 then this should work. I'm not sure why that's not working, but I can't be bothered to figure it out because there's an easier way. We can basically just swap these around. We can say if shape.kind equals square, then check the side length there. Otherwise check for a circle.

03:06 So this is kind of a way to extend our discriminated union and add a default for it where the one that has the optional kind property is the one that is the default. If we had more than one optional thing here, then it sort of wouldn't work, right? And it kind of wouldn't work on the outside of the function too, because we'd be able to pass in side length here and

03:26 stuff. So having only one optional kind is really important if you want a default and a discriminated union. This is super useful in all sorts of abstractions where, for instance, you have a button component where you might want to make it into a link. So you maybe add an as link or something, but by default you want to treat it as a button. So again,

03:45 defaults in discriminated unions are a really powerful way to extend this abstraction to cover even more possibilities.