This course has been updated! We now recommend you take the React and TypeScript, v2 course.
Transcript from the "Typing Reducers" Lesson
>> So we've done a lot with useState. Now one of the things that we might wanna look at is reducers. And this is one of the places where I do think that TypeScript kinda shines. One of the criticisms levied on Redux, and this is not necessarily true as much anymore and also true of some of the useReducer stuff in React, is I think that the reducer pattern is incredibly powerful, right?
[00:00:37] Use state is useful for like, yeah, I wanna figure out that answer so that Avengers quiz is toggled or not. Yeah, useState is good for little stuff like that. But if you find yourself putting seven useStates in a component, it's probably time to rethink your approach a little bit.
[00:00:56] useReducer is definitely helpful and Redux, someway we're gonna tell how it applies to both. useReducer is super helpful for more complicated state management. Where you might have multiple things and you're like, okay, based on three different properties, I want to determine the third one. And that's where it can become super helpful, but there's a lot of ceremony that we do in reducers to basically protect us from ourselves.
[00:01:31] A lot of times you'll see action types are usually strings, but other times we'll assign them to constants. That way, if we mess up spelling one of the action types, it doesn't fall through our conditional somewhere. A lot of times it's like either you misspelled the action type as you dispatched the action or you misspelled it in the reducer, and all of your conditionals, or your case statement, it falls through all of them, and nothing happens, and there's no error message.
[00:01:57] And you're like, I don't know what to do. And this is an area where I think that TypeScript definitely begins to help us. All right, so here's another example application. This one is a pizza calculator, the Jock. But when I used to work at a coding bootcamp where we did meetups and stuff like that, it took a bunch of smart people way too long to figure out how many pizzas we should order, right?
[00:02:23] So we'd literally made a silly app where you put in the number of people, how many slices of pizza you think they're going to eat, and you would get the right number of buys. This application is ultimately not successful because we ordered from the pizza place so much that they started throwing in free pizzas so that eventually we had a feature request to add like a guess on how many free pizzas that we're gonna throw in and so on so forth.
[00:02:44] But generally speaking, this is an app that we use. So here you can see number of people, okay, there should be eight people. They're gonna each eat 13 slices of pizza, 13 people, 13 slices of pizza. Another feature request that came up was how many slices per pie.
[00:03:03] But anyway, it works. Now, I've cheated a little bit. If you look closely at the reducer, it's just any all over the place. We've done a little bit of type scripting, and I will talk about some of the stuff we did. But then we're gonna add some type safety, and we're gonna see some of the kinda cool stuff that we get for free as we go along here.
[00:03:25] But let's take a tour of the code as it stands first. Give myself a little bit more room here. All right, a PizzaData. Nothing special about that. It's number of people, slices per person, slice of a pie. PizzaState, well, this is interesting, right? Cuz really, there's a fourth PizzaState.
[00:03:44] There's three input fields. But there's a fourth piece, which is, what is the total number of pizzas? Here, you can see we're gonna use this intersection here, which is we're gonna say, cool. What I wanna do is I wanna take my PizzaData. And I also wanna combine it with this other type, right?
[00:04:02] And this is our first kind of foray in kind of determining types based on other types, programmability around our type system. So PizzaState is simply going to be everything in PizzaData plus one more field, which is the number of pizzas needed in number. And this is super useful because if we change something about the PizzaData, our PizzaState type would inherit that in this case cuz it's a combination of those two objects.
[00:04:31] calculatePizzasNeeded is a very proprietary formula that multiplies number of people time slices per person divided by the size of the pie and rounds up, because you cannot order two-thirds of a pizza. So it rounds up to the nearest pizza. And this is just a helper function that I'll use my reducer to update the object.
[00:05:19] Anytime one of these input fields change, basically recalculate the number of pieces we need based on the new data. And then finally, the end result. We have got a bunch of anies here in the calculator, we're gonna have to handle all of that momentarily as well. And then we've got kind of the highest level here.
[00:05:43] So we got useReducer. And right now, it is of the type of any, and you can dispatch anything. And what's gonna happen is, as we add some more typing to this, we're gonna see how TypeScript begins to help us from common mistakes that we will all like to admit were less common than they really are, right?
[00:06:05] But little mistakes that in the course of my day to day, I make all the time. So if we go ahead and look at our reducer, right, and this is kinda where it becomes somewhat interesting. I can go ahead, I can change, we have a type around our PizzaState.
[00:06:21] So I can go ahead, I can say PizzaState in this case, right? And the interesting part is all I said for this reducer function is that the state argument, the function is going to expect that it is going to get PizzaState passed into it. If I go back down to application, you can see that TypeScript has already figured out that even the initial value of state in this case is going to be a PizzaState, right?
[00:06:52] In some version, it's evens like, if the reducer always takes this argument and it's gonna either return some version of it or the state again, if none of the action types hit, then I know the state is going to be a PizzaState. And it has already figured out the type of the state of our component, right?
[00:07:13] And so you see PizzaState is a reducer that takes a pizza state. So I've got an action, we haven't figured that out yet, and returns a PizzaState. And initial state fits in with that as well. If I just passed in some other object, TypeScript is already gonna save me from myself.
[00:07:27] The interesting part, though, and this is kind of a interesting thing about TypeScript, if I just took this object, right, so right now we're saying the initial state is a PizzaState. But what happens if I just took the object and passed it in, all right? TypeScript's happy with that.
[00:08:10] It uses structural typing set, which is, yeah, you didn't say this was a PizzaState, but it is, right? I can figure that out for you. I will accept this. This is totally fine, and it totally works for us. Right, let me put it back in initial state, then we are good to go.
[00:08:25] But we still need to deal with the fact that we don't have this concept of a pizza action. Let me go ahead and pop that in as a PizzaState as well. So now, the calculator is simply that pizza's down here. Okay, so this idea of a pizza action, let's actually go ahead and define this.
[00:08:49] A lot of times what you'll see people do is, because you can easily mess up, in fact, the one I first somehow can't spell the word number a lot of times, so I'll forget the letter B. This is happened to me more times than I want to admit.
[00:09:32] With TypeScript, we don't need to worry about that cuz the type system is going to help us. So let's make a pizza action. We'll make a pizza action and it is going to be of the type. Well have update number of people, let's just do a little copy paste action.
[00:09:54] Or it's gonna be UPDATE_SLICES_PER_PERSON. Or it's going to be UPDATE_SLICES_PER_PIE, right? It's either gonna be one of these three things or, We're not gonna be okay with this, right? And then finally, it's gonna take some kind of payload, Which should be a number since we're doing math with it.
[00:10:22] Right, now we will say that the action that we're expecting should be a pizza action. And what's cool about this is now if I misspell something, TypeScript will catch it. There's no world that this should be allowed, here are the types that you said were okay. That is currently not the case.
[00:10:47] And it's kinda catching us before we have to figure out like, I dispatched the action, nothing happened cuz it fell through that condition all the way down to return state. And that's not cool. The other interesting thing is that let's say hypothetically I removed these two for a second, right?
[00:11:06] That kinda changes the way this works, cuz now I can return PizzaState or undefined, right? We can kind of see that as well, and useReducer here is now angry with me. It's like no, no, no, no, no, the way this works, is I need to get back the state.
[00:11:20] So it'll begin to catch some those edge cases if you haven't covered. We've got this fallback where it'll always return the state. But it will begin to save me from myself in some cases, and again, catch those little things that as we're writing code, we're trying to think about the problem, we're not totally aware of, all right.
[00:11:40] So we need to know how to type this. Now, it's not a pizza action, cuz it's the dispatch function. But is it just like react.dispatch, right? Is that it, right? Well no, that's not it. So we gotta think about this for a second. Again, the skills that I wanna teach you is the tools.
[00:12:08] So if we look, we can see that dispatch is a function that will dispatch pizza actions, right? Why, because it knows that the reducer only takes actions of the type PizzaAction. TypeScript has figured all of this out for us. So now we know exactly what the type is, we can go ahead and steal that for ourselves.
[00:12:30] We're gonna dispatch, PizzaAction, right? And now we've got a new problem, and this is a problem that we've seen before that TypeScript just figured out for us, which is these payloads all have red squiggly lines under them now. And the reason they have red squiggly lines is because the payload is expecting that the payload is a number.
[00:12:59] Again, it's a problem we had before, which is number fields make strings. And we're good to go, right? And now we've added some type safety here. Again, we can't misspell a action now, right? We know that it's gonna basically run through, again, this is a small application. In a larger application, it's gonna still run through everything and make sure that we haven't made any of these tiny little errors that can sneak up as the complexity of application grows.
[00:13:27] And yeah, if we misspell anything along these lines, if any of the wrong data is there, even if we misspell what we dispatch, a lot of times you'll see in applications we'll use action creators, and action creators is still probably a pretty good idea. But even if I misspelled any of the action types here as well, it'll catch it immediately.
[00:13:46] I don't have to wonder when I go to run my tests or anything along those lines. One of the interesting things about TypeScript to think about is how many unit tests were you writing that we're just catching weird edge cases? The type system can effectively try to catch for you.
[00:14:03] That's not to say that you don't need to test, but it does mean that there's a class of things that you don't need to test for anymore. So my argument would be some of the extra work that involves in having a TypeScript application might be helpful, there are things that are gonna save you some work other places.
[00:14:27] So one of the questions, and we'll actually get to this after the exercise a little bit, but I'll answer it now as well, is what happens if there are different payloads? In this case, let's say it should be a number or a string. Or let's say my payload was a simple thing, it could be something like, the value should be a number and the, I'm having a hard time live imagining what another thing the payload might need is location, I don't know, as a string.
[00:15:02] And then you wanna have, or possibly just, you could have other data types in here as well, so you can kinda define that. And we'll actually see a trick after the next exercise of how to get a little bit more interesting around this. But you can basically kinda define the shape, or you could have two different kinds of actions be like, it takes either PizzaAction or something, where it's got a very similar shape and say like, now this is this, or other action, right?
[00:15:28] So you can have a few different action shapes, we'll actually see that a little bit more after we do our next exercise and then we'll be able to rock and roll. So the question is, it seems like because TypeScript is so ingrained in every piece of data, it changes the shape of every piece of data, could create a snowball effect.
[00:15:47] Have you encountered this or is it not a problem? Well, the interesting part is our data is deeply ingrained into our applications. So prior to TypeScript, you might change the shape of your data and then hope that you had enough test coverage to track down all the places where it was important.
[00:16:08] One of the things that we're gonna see later in this course is how we can be a little bit more flexible with our types, we kinda saw that with the PizzaState. It was based off of the PizzaData, right? And so PizzaData changes when PizzaState changes along with it.
[00:16:26] Well, a lot of times many of our applications, the data, and especially in larger application, is tightly ingrained with our application. And we hope that when we update it, that we've tracked it down and found everywhere. TypeScript will at least make it very obvious and give you red squiggly lines all over the place where that has happened.
[00:16:48] So yeah, a lot of that is is depending on the context and stuff like that, but generally speaking, I think that TypeScript aids in the debugging, and it gives you in the same way unit tests give you more confidence that that change you made didn't have unintended consequences.
[00:17:03] I think that the type system in TypeScript also gives you the confidence of yes, things broke, maybe that's a good thing, right. But yeah, so I think that yes, it can get a lot unwieldy, it can snowball. But I would argue that the problem with snowballing, before you had TypeScript in there, you just had to do more work to assess it all out.