React and TypeScript, v3

Discriminated Unions

Steve Kinney
Temporal
React and TypeScript, v3

Lesson Description

The "Discriminated Unions" Lesson is part of the full, React and TypeScript, v3 course featured in this preview video. Here's what you'd learn in this lesson:

Steve introduces discriminated unions by creating interfaces for different actions like increment, decrement, and set count. He also explains how TypeScript narrows down the possible types based on the action taken, ensuring that only valid actions are handled in the reducer function.

Preview

Transcript from the "Discriminated Unions" Lesson

[00:00:00]
>> Steve Kinney: With that, this can be kind of our basic action, and that's fine. This is now a string that doesn't really get us a lot of like wonderfulness, but it's better than what we had before. Where this becomes super interesting is when we kind of like take objects that are very similar in their shape and use them in a union, and that's what we mean by discriminated union, right?

[00:00:31]
And so if we said something like, I'll use an interface here because I think it's easier, interface increment action. And you can do it with a type just as easily, extends action. But the type is going to be increment. And you can do that for the rest of them too. I regret turning off Copilot because like getting that to automatically do that for me right now would have been great.

[00:01:03]
Doing decrement action. And because I type is decrement and I'm going to make one more actually. I'm going to make one called set count action, which is going to take the type. And that is going to be a set count. And we're going to say that the payload, I could make it an object like that. I'm just going to say it can be a number right now because I'm very lazy.

[00:01:45]
Right, so we have effectively, what are you angry about interface, yeah, if you start talking and stop typing, things don't work. Cool, cool, cool. And so we have these where a discriminating union comes in is we do this now. We'll say type counter action which is either an increment action or a decrement action or a set count action. Right. What is happening behind the scenes is it now knows that type can only be one of these three things.

[00:02:35]
So if we look, we say, oh, I actually set it in the function though, just typing in here doesn't do anything for me. We'll say it's now a counter action, and you'll see a few things. One, reset, update draft count, and update count from draft are no longer valid action types, right? Because action type can be one of three things, increment, decrement, or set count.

[00:03:02]
Right? So these are no longer valid in this case. So that's cool, so let's get rid of those, and we'll say if action type equals equals equals. Two things to notice here. One, again, no Copilot on, no Cloade code, no Cursor, nothing, right? Mostly so I can make points like this. It knows that it should be set count. And you're like, why just set count?

[00:03:42]
If I was to move the same line up here it could be any of the three. If I were to move it down here, it can only be two of them, right? And if we read the code, the logic makes sense, which is we said if, if it's increment, do something and return, which means by the time the TypeScript has gotten here, we know it can no longer be increment, right? And so we know it could either be decrement or set count.

[00:04:19]
And then finally, when we're down here, we know that it can only be set count because we've returned if it was increment, we've returned if it was decrement. TypeScript is like whittling down the options as we go along. And so we need to set type or action type is set count and knows that. And we can come up with whatever the new logic is, and we'll say new count equals equals equals, or no, that's not how that works, just because I typed equals equals that many times doesn't mean that's true anymore, is equal to action payload.

[00:05:00]
And the other interesting thing that we'll notice is that it knows that action payload is a number, because it knows that it is this last one, and the payload of that one is a number. If I did like a console log action payload here, it's unknown, right? And the reason for that is because the default action that we're extending from says the payload is unknown.

[00:05:32]
So we don't know what it is. But we said, if it is a set count action with the type set count, then the payload is definitely a number. We have one more special type that we can use to kind of also just make our own lives easier. This code will work. You can stop here, but what if you wanted to like go the extra mile? There's another special type which is payload never, which means this can never be set as a value.

[00:06:06]
And so now, like, down here, it knows that this is just not even a value that we can use, because increment and decrement actions don't have a payload. So if we tried to say like, count minus action payload. Well not entirely sure why that works. Play with it in a second, but like, theoretically it cannot exist on that object. I'm kind of curious, like, I think I can't pass in one that has that either, but we'll see.

[00:06:40]
Anyway, so we can like begin to define it because I think you can't actually pass one in at that point, but like it's kind of curious. I don't know what happens if you subtract never on it. Also, I think it's because it doesn't do anything bad in that case. But fun thing that I just learned now. Anyway, we will do the negative one in this case as well.

[00:07:03]
And the other interesting part is if we said that the, we don't actually need this draft count since we have set count. So let's just say that the state is always going to be this count. Just like that. Now that's no longer on there, and we will always, the thing about TypeScript is you can have extra properties that you're never using, and it's like, as long as you have the ones we have, and there are ways to like say that it's more strict than that, but we'll stick with this for now.

[00:07:48]
We'll return the new count, so on and so forth. Here we'll also, right now, because we don't define the return type on this, it's trying to look at the code and see like, in most cases we do return the same thing over and over and over again. So like, it looks like it will effectively always return a count number. What's super interesting is, let's say I said like, no, you definitely need to return something that has a count and a number.

[00:08:26]
And I, let's take all of this out. It's now upset with me. It's like, yo, you said that this function returns an object with a count property that is the number. Looks like you handle two cases and then you don't return anything, which means like, it's like that's not, you're not holding up your end of the promise. What's interesting though, is, if we turn this to a switch statement and like we say like, action type, I hate switch statements, but case is.

[00:09:18]
Oh, look at that, we get all of this fun. And it will make sure I didn't make any boo-boos, which is great when you're coding in front of your friends. Decrement and knows that and you could see that was only two of the choices, right? I got the math right. It's not going to help you with the math, so that's good. Knows it was that final one. And it knows that action payload is in fact a number, so that is valid.

[00:09:58]
You'll notice that this all became grayed out because it knows that this is unreachable code. We didn't have to do that thing where we have a default value, we didn't have to do any breaks because we return, and we don't even need to return something at the bottom here because it knows that of all the things that an action type could be, you handled all of them.

[00:10:28]
Thereby, you don't need to do like return state at the bottom, right? You can, but it is effectively unreachable code. It knows that every possibility here was accounted for, and that means you know that every possibility was accounted for, right? And you know that there's not just like one extra thing. You know that if somebody mistypes the type and they try to pass it into this reducer, it's not going to work, right?

[00:10:58]
You get a lot of like security and safety. And again, like we saw earlier, right, we do, dispatch here and we say. One, we can see that type is something that autocompletes. This is something that autocompletes, so we said increment. And then if we tried to say, what I say though is it count as the. Yeah, I can't even put in that count, right? Because that was defined as never.

[00:11:37]
It's like, yeah, yeah, but if I turn this into set count. One, it's like, yo, you still owe me more information. And that payload, if I hover over it, I can see, oh, it's supposed to be a number in this case. Then it knows it takes a payload that's a number, right? Because the other two options have it defined as never. So all of a sudden, you know, if you had all these different like actions in your reducer, the moment you type the type, it knows for that given particular action type, what everything else is supposed to be, right?

[00:12:14]
And you again, if you make a mistake in the property name, it will tell you. If you make a mistake in the value, it will tell you. If you get a value that, you know, looks like a number but is actually a string, it will tell you, if you get something that could possibly be undefined or null, it will tell you. And you get a whole bunch of extra safety, and again, in a lot of ways, self-documenting code, right, just enforced by your type system in this case, right?

[00:00:00]
And like, it's an incredibly powerful pattern because now, right, you can just have this dispatch here, right, that takes a counter action, right? And so really, you could have all, like, let's, you know, let's follow this through.

Learn Straight from the Experts Who Shape the Modern Web

  • 250+
    In-depth Courses
  • Industry Leading Experts
  • 24
    Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now