Check out a free preview of the full React and TypeScript, v2 course:
The "Adding Types to Reducer Actions" Lesson is part of the full, React and TypeScript, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Steve demonstrates how TypeScript can provide an explicit list of actions for a reducer. This is better than using enumerated values because both the action name and payload type can be specified.

Get Unlimited Access Now

Transcript from the "Adding Types to Reducer Actions" Lesson

>> I would love to give this a type. But as you can see, I have some things to work out here, right? Well, it could have any one of these strings, and it has a payload if and only if it's updateDraftCount, right? The rest of them, it doesn't have a payload, right?

[00:00:22] And you're like, cool, cool, cool, at least you can get autocomplete on the type, right? And make sure you have the right properties, wait. So there's a bunch of ways that I could do this, right? The most naïve way, which we'll skip to the second one, the most naïve way is to every case, we'll have a increment action type, and we'll have a decrement action type, so on and so forth, right?

[00:00:47] The cool thing about TypeScript and its ability to figure stuff out, we kind of saw this before where we said, if there's no quote, return, and then it knew what quote was, right? You can give us some information about the problem at hand and it can kind of figure out the rest.

[00:01:05] So I could say, I could have, let's say, type, I don't know, we'll call it, let's just call it, I'll just call it action. And this action can have the type increment. I might break this, I could break this down, I'll just type increment, decrement, Reset. Let's see, what were the other ones?

[00:01:38] The ones that didn't take payloads. I think we're just updateCountFromDraft, cool. I gotta watch where I put my cursor, and I can paste this in, right? And those don't have a type. And then the one that had to type, it's gonna be a little bit different, which is, type ActionWithPayload.

[00:02:09] And in this case, we'll say that the type is just this one. And we'll go ahead and say the payload in this case is a number, cool. We gotta put some equal signs in there or else things won't work. And where did I leave dangling code otherwise? Down here where I got stuff like half done, so we'll take that out for a minute.

[00:02:35] Yeah, cuz that's from my previous iteration earlier, we'll get all that wired up in a second. That's just a half written, cool. I'll pull that back in. And what I wanna show you is a few things. I was gonna rewrite this one, for instance. Let's take out the body of it so I can use it again.

[00:03:11] Action.type, yeah. Oop, let's actually pull out all these for a moment. I didn't replace any, so let's do both. We'll say that this is now either one of these actions, Or that action with the payload. And if you look at some of them, Redux, for instance, has abstractions to figure this all out.

[00:03:36] If you use their create action in Redux Toolkit, it'll automatically type stuff for you. But now let's look what happens. In my reducer as I'm writing this logic, I go action.type is a new type equals, look at this, cuz I've misspelled things before and it falls through, right?

[00:03:58] I can do increment, cool. And then we'll say, we'll paste that in. And then check this out. action.type ===, you know what's not on that list? Increment. You know why it's not on that list? Cuz I already returned from that. So it's not an option anymore, right? Cool, and so you can imagine now as I bring these all back in, and I say if ( action.type0, it's gotten nothing for me, right, cuz at this point, action.type is never, because you handled all the cases it could possibly be, right?

[00:04:42] And then, if I go in here and I want to dispatch an action, Cool, I get type immediately. I get all the values. But let's say I use Increment, And I wanna put a payload in. It's like, no, cuz that's not one of the ones that takes a payload.

[00:05:08] Or more importantly, let's say that I want the one that takes the payload, and I forget that it takes a payload. It's like, yeah, you can't do that one, right? And so the amount of mental space that you do not need to reserve for keeping track of that complexity, it's just handled for you, right?

[00:05:32] Because the union type of action, action payload is like, cool, if this first part matches, then it matches the fact that it has no payload, right? Moving on, if it matches this one, then it has to have a payload of a number. So types who kind of does some algebra to figure this all out for you and of all the possible combinations, right?

[00:05:52] It is not a combination where you have increment in a payload and there's not a combination where you have updateDraftCount and not a payload, right? So as you go to use it, if you're saying that it is the union of these two types, then you get the fact that it will figure that out based on the information that it has, right?

[00:06:10] And that's kind of above me on like, is my number a number, right, where you start to kinda tap into a lot of the power here as well, right? And everything is kind of keeping track. And so some of those silly mistakes that you might make, right, and it's one of those ones if you have not made that thing already, you definitely will.

[00:06:28] And I think the other really cool thing is then also one of the criticisms of reducers and particularly popular with the criticism of Redux was that there was a lot of boilerplate, right? To stop yourself from mistyping an action, what people used to do is const INCREMENT. And this is the best use of my time at my salary level, right?

[00:06:52] Cuz if you misspell a variable name, JavaScript yells at you. But now you can't misspell this. I mean you can, but you will immediately know, right? And so a lot of the extra work is solved for you as well, right? And having an autocomplete when just using dispatch, either, a, removes the need in a lot of cases for simple cases for action creators.

[00:07:17] I still like them cuz you can do a thing where it's like, hey, automatically type the action for me based on stuff, right? I'll show you an example if we get in a second later where there is createAction in Redux toolkit that's like, hey, just give me a function about what arguments it takes and what it spits out and I'll give you the type for the action immediately.

[00:07:37] But a lot of cool stuff, but some of the power, one, saves you in code you are gonna type anyway to make your reducer safe, but then also, I have made the mistake of mistyping a case. The other kind of example, and I'm gonna see if I have one written up or yeah, no, I gotta write it myself, but I'll do a simplified version.

[00:08:01] So we'll comment this out for a second. And I could theoretically get away without the return state since all cases are handled if it was a If-else, but I'll show you where this also becomes powerful. We all know that the best practice in switch statements is to have a default case, right?

[00:08:17] So if we do switch(action.type), we will do case. I love this, right, and I don't know. We'll just return the initial state each ones I don't have to remember how to add numbers. Decrement, So we do, yeah, return the state as I like off script, so we'll find out together See if I made mistake in there.

[00:09:04] Probably made a boo-boo. I'll pull it out in a second cuz I greatly prefer If-elses, so my ability to write a switch statement is slightly less than great, but a lot of times, it'll say, yo, you don't even need the default state, right? It was curly cuz it'll figure out that it was never.

[00:09:25] Say I have a screenshot of it, but, It looks about the same maybe, cuz I didn't write real implementations in here. But the major point versus watching me do a switch statement repeatedly is that it'll analyze the code to figure out, have you dealt with all the cases?

[00:09:44] Are you aware that everything is gonna happen here, right? And then figure it out for you. In the same way that action type became never, right, you can have a world where also you don't need a default case cuz it knows that it hit every single case along the way.

[00:09:58] You can't dispatch a bad action that would pass through, because it knows what the valid actions are, and it will analyze your code as you pass it in. You can't forget a payload on a valid action if it requires one. And even though it's a less bad scenario, you can't accidentally put a payload on the wrong action, right, which is sometimes we'll catch you of even, let's be clear, were updateCountFromDraft and updateDraftCount my two best names ever?

[00:10:24] Probably not, right? Even as I was live coding, was I slightly where I was gonna mess one of those up at some point? Absolutely, right? But the idea that I couldn't dispatch the wrong action with a payload is incredibly powerful, right, and we can kinda have all of that in place.

[00:10:41] So let me put back my if-elses because I like the more honestly, and I'm not rewriting that code. I'm not rewriting add in front of you over and over again. So we can pull this in. Right, and I know that I've got type safety here, right? And there's a bunch of really interesting, I don't know if I can do it from scratch, but I'll try to get the concept enough.

[00:11:09] Okay, we can go look at these different objects and derive a type from it. I probably can't do it live, I can look it up in a second from the last time I wrote it. Or we can look at createAction from Redux toolkit. But there are other ways that you don't even have to type this much.

[00:11:26] But to your question earlier, getting all that stuff for free in Redux toolkit, cuz React Redux and Redux toolkit were rewritten In TypeScript a year or two ago, right? So they have full TypeScript support out of the box, so you get all this for free. So would I get super fancy with crazy dynamic types if I was just doing a form validation?

[00:11:49] No, right? The last thing I'll show you at the end of this workshop is this crazy thing, you can do polymorphic components and even the example that inspired it. I went to look at their source code and they have a hard coded list, right? Sometimes, it's just easier to be explicit than to be totally dynamic.

[00:12:06] So for short thing like this will give you all the safety and it will stop you from the stupid mistake that I made. And it's not that much more work than the safeguards we were putting regular JavaScript, plus you get autocomplete in that sense. Plus it will protect you from extra properties in a way that even a lot of our safeguards don't, because you could be like, okay, we'll throw an error.

[00:12:28] Yeah, that's great in development, right? You've gotta be a little bit careful how much you're throwing errors around when you push it to production, right, cuz it's like taking the whole UI down. It's usually not great, it's usually frowned upon, so it gets a little bit trickier. But the amount you get for free by just one, I think we saw two things, what an any will do to you, it infected everything, and it made it so I made a boo boo preparing this.

[00:12:56] And two, just the amount of, even autocomplete and knowing that I handled all the cases and stuff along those lines is incredibly powerful. And this is my point that I made earlier in the workshop that, yeah, having to have the type and not just doing whatever you want, I guess, is a slightly more typing, pun unintended.

[00:13:18] But the payoff of typing, you save later, and also weird head-scratching errors, you have to throw console logs in. I am aware that the exists, I don't care, saves you later.