In this session geared towards devs with prior experience building React applications, let’s answer those questions. We’ll walk through the common bugs that infect our apps and learn how the use of strong types with TypeScript can help prevent them. After the session, you’ll be itching to try it out in your next project!
Well, hello there. Welcome. First off, I want to say happy fifth anniversary to React Summit. Happy birthday. And welcome to all of you to TypeScript plus React equals love. I kind of actually wished that I would have used a fire emoji instead because that's how awesome I think the partnership is between TypeScript and React, but I stuck with the heart emoji. So I want to spend our time showing off TypeScript features that can prevent bugs in our React apps.
So I'm assuming that you have developed in React before, but you know little to no TypeScript. Even if you do know lots of TypeScript, you'll get lots out of this. But for those that don't know TypeScript, I'll be explaining the concepts as we go. And just so you know, these slides they're already online. If you visit my site benmvp.com, you'll find a link there, or you can follow the Bitly link that's at the bottom there.
All right. So just to introduce myself formally, my name is Ben Ilegbodu. I am a Christian, a husband, and a father.
Real quick, this is my family. That's my wife, Rashida. We've been married 10 years, just last September. That's our oldest daughter, Simone. She is six and a half. Our middle daughter, Avery, who just turned four last month as well. And at the bottom is our son. He's Usher, he's a little over a year and a half, and we're still trying to help him smile in pictures. We're still working on it. So we live in the San Francisco Bay Area, a town called Pittsburg, California. So not Pittsburgh, Pennsylvania, Pittsburg, California, without the H. I am a principal, front-end engineer at Stitch Fix. And I'm also a Google developer expert and Microsoft MVP, both in Web Technologies.
React plus TypeScript business
All right. So enough about me, let's dive into this React plus TypeScript business. So one thing I want to make clear before we begin though, is that a React component is just the function. There's nothing really special about it. It just takes in props and returns JSX. So it can be treated and typed like any other types group function. So to start off, you can use an interface to define the props as you see here.
And it becomes the type of the props argument that gets passed to the app components. So you can name the interface anything you like. I chose AppProps here just for an example, but I actually tend to use just props and you will see that in the following slides as we go.
Benefits of using TypeScript
1. Props must be listed
Okay. So the first benefit, let's get right to it. With TypeScript props cannot be used within a component without a definition.
So like how many times have you had props in a component used without a prop type definition? Right? And in this case, we're trying to use props that loading without defining it and the props above, and that becomes an error.
So there are various ESLint rules to try to catch these sorts of things, but they can be limited. In fact, they're limited in how you call the prop.
So similarly, when you can't pass a prop, if it hasn't been defined, right? So here we're trying to pass counts, but count wasn't in the interface for app props. So how many times have you seen a prop been passed to a component and it's not in the prop types and it doesn't seem to be used in the code, but you're afraid to remove it because you're just not sure. Well, TypeScript gives you the confidence that you can remove it because it wouldn't have allowed it in the first place.
So this error message at the bottom can seem a bit cryptic, to be honest. Property count doesn't exist on type intrinsic attributes and app props. But as you encounter them more and more, you'll get a little bit more familiar and used to them.
2. Props are required default
Okay. The next one, so React prop types are optional default when you list them. So I see lots of examples where prop types are defined actually, but none of them are marked with is required. But if you look at the code, the props are definitely required. They're calling dot map on an array and things like that. These are bugs waiting to happen.
So TypeScript interfaces are required default. So without doing anything special, you're guaranteed that the values will always exist. That's super nice. So if I call this component, leaving off the required prop, count, in this case, it will yell at me. And again, it won't compile.
So you can use the question mark to denote that our prop is optional, which means that its value is undefined when it's not passed. So now if I omit the count prop, when rendering app, in this example, there's no errors because it'll be defaulted to two.
3. Prop refactors are caught
Okay. If you change the name of a prop, all the places using it have to be changed as well.
So let's say this prop right here highlighted was originally names, but I changed it to players within the component. So you go and you search and replace to fix all of the issues, but did you get them all? How can you be a hundred percent sure? What if somebody was doing something crazy and it didn't match your regular expression? Well, TypeScript will complain if you miss a spot. And actually a derivative of this is when you simply mistype a prop, TS will complain immediately as well.
4. Can't avoid defining complex objects
Okay. Number four. So it's a lot of work to define a deeply nested shape, right? Even with ESLint rules, we find ways to cheat. There's nothing forcing the prop types to be a 100% accurate.
Well, TypeScript is now going to get in your way and prevent you from being lazy. Prevent us from being lazy. Let me put myself in there too. But it's also saving you because you have to define exactly what's available. You can't access properties off the user prop unless you define exactly what they are. So if we decide, and the address interface to rename is primary to just primary, we'll get TypeScript errors all throughout the app and the app component until we fix those. So again, TypeScript is being very beneficial in refactors.
5. Function props have explicit signature
Okay, so the next one. And this one actually is probably my favorite.
So with prop types, all you get is prop types that funk for a function, right? There's nothing that tells the user of the component what parameters it'll pass when it's called, or if it expects something to be returned, right? All you get is that.]
Well, now in TypeScript, you have to define both the arguments as well as the return value. And once again, if we decide to add a second parameter to on change, for instance, or change the types of the parameters, TypeScript will error out unless we fix all those places. This happens all the time, right? How many times have you forgotten to change a function handler in some places, right?
And the thing is about functions is that they're usually called as a result of a user interaction, meaning you're less likely to hit this error while manually testing. So now you're relying on your great test coverage to catch these errors and we know how that goes. Right?
6. One with the other
Okay. So let's take a look at an advanced pattern, right?
Sometimes you have a component and it has dependent props. So let's say you have this text component that allows you to truncate text with a truncate prop, right? And it also has a show expand prop to provide a link, little link to click and expand the truncated text. Well, the show expand prop doesn't really make sense without the truncate prop. So you want to make that configuration an error, right? It's a much better developer experience for users of the text component, if that's the case. So exactly how do we make this possible? How do we get this error at the bottom for those invalid configurations? Well, there are a couple of ways that you can set this up and this here is my preferred approach.
So first, you define your common props, which are the props that will always exist no matter what, right? Then next, the truncate props type is what's called a discriminating union. Okay? Fancy term, but it just means a number of objects combined together, one or the other.
So then within that, first, for the truncate prop, for when the truncate prop is false or undefined. So it's not specified. So in this case you were going to set show expand to be undefined. So that translation basically is that show expand cannot be set when truncate is false or undefined. All right?
And then second is for when the truncate prop is specifically set to true, and only true, so when it's turned on. In this case show expanded kit is an optional bullying. We're now allowed to make this extra configuration. So then, props is the intersection or the combination of common props and truncate props. It's basically all of the props together. So then finally, in the code, both truncate and show expanded are typed as optional bullying. So you can use them as you need inside of the code and check to make sure that they exist as necessary.
7. Extending HTML components
Okay? So then, let's look at another advanced pattern.
Let's say I've got my button component, right? That's a wrapper over HTML, an HTML button. It has some props to control the visual design at the top, like variant and size. But I also want to support all of the button elements props, like being able to pass and type, on-click, disabled, et cetera. And of course I want all tight checked, right? Because this is all about type safety.
And it will complain in the error console. Well, let's try to make this type safe.
Instead, this is what our TypeScript definition could look like.
Again, there are a number of different ways that we can accomplish this, but this is the way that I like to accomplish it.
All right. So first, you define whatever are the new props and I'm calling it new props as the interface. So in this case, this will be variant inside. These are the stuff that's on top of what a button element has.
Then we want to define props third type as the intersection of new props and all of the button element props, right? But there may be a chance that the button element already has a variant or size prop in it. So here's the catch, in which case we want to override, right? The variant and size with the ones that are in new props.
But when there are name collisions and interfaces and types, weird things happen in TypeScript. I'm not quite sure what those are yet, but I know that they happen.
So we want all of the button element props, except the new ones that we're defining, right? So that's what this line is doing.
We use key offs to get all of the prop names out of new props. That would be variant. That would be size. Then we remove or omit those props using the omit utility generic. So we remove them from the button element props.
Then finally we merge those in to the new props and we have our type for our props for button. Then finally in the component code, we can spread button props like we always do, except now button props is fully typed. It knows that type is in there on click is in there. All of the attributes that a button has.
So now the user is a button, though, wouldn't be able to specify an href prop for instance, or some other prop that doesn't exist because that is not on the type and we would get an error.
8. Parameterized props
So then lastly, my last feature that I want to show you, is, let's say you have a list component that has a render prop for each item, but the list is generic so it doesn't know what sort of items it's getting, because it doesn't really care about what items is getting. All it's doing is just displaying it and then maybe having dividers in between or something like that. It's the render prop that does the work of rendering the actual UI of the items. So my biggest beef with render props in general is that I literally have no idea of what I'm getting when I'm not using TypeScript.
So now with TypeSript, in this case, the list needs to be able to handle different types. So on the left, we pass an array of strings and we call it dot length on each item. On the right, we have an array of numbers and we call dot two fixed on the data just to prove that recalling different properties. So we want to be able to know the type of the item param in the render prop based upon the type of the items prop, right? We have to have that relationship. And in this case we're not typing what item is so it's all based upon what's in items.
So you can imagine if items were an array of objects, let's say, how much more necessary this sort of functionality would be.
So here's how we make it happen. Like I said, a render prop is just a special function that happens to return React, but it can be typed just like any other prop function.
And with the power of generics, it can be generically typed. So the list component first defines a generic parameter T that's in the angle brackets there, it then passes T over to the interface so props of T. And then third, it says that the items are an array of these T types. So we don't know what T is, but it's a generic type.
And then next, the render prop is going to be passed items of type T. So T is malleable. It can change depending on how the component is rendered. So when I render a list and pass strings, T is now a string, but when I render a list and pass numbers, T is now a number. So list is generic or parameterized as I call it. So, one quick thing I want to note is that this little angle bracket T comma bit, it's a little weird and not normally what you would do at generics, but the comma is necessary when you define a component using arrow functions.
Otherwise the parser can't tell the difference between JSX or an arrow function. So generics are pretty mind bending at first, as you might be feeling if you've never seen them before, but they're also really critical and helpful and shareable code.
So I've included this link at the bottom as a resource for you, TypeScript generics for people who gave up on understanding generics. Pretty Epic.
Okay. So I've got some other resources here as well for you.
The most helpful one will likely be the one at the top, the React TypeScript cheat sheet. It provides lots of recipes for common situations for you. I also only talked about function components, but TypeScript does work for class components as well. I mean, hooks are the future, so you should be using functions, but if you want to use classes, some of these resources have examples for you there.
So before I finish though, I periodically host a series of short three hour remote React workshops. I call these mini shops and one of them is called TypeScript for React Developers. So if you're interested in a more hands-on learning of TypeScript plus React, doing exercises and such, you should check it out.
Just visit my website, benmvp.com and you'll see them listed there. I have others as well that you see there:
So, what I want to let you know, though, is that I'm doing a free giveaway for the conference attendees to celebrate this fifth anniversary of React Summit. So, if you go to my website again, benmvp.com, go to the mini shops page, find one mini shop that you like, and that you can attend and send out a tweet with the link of it, tag me in it so I know you sent it out and I'll pick one to give a free ticket. All right? And bonus points, if you can actually include a selfie of you watching this talk or during the Q and A, all right? Free giveaway there.
All right. So, that's it. I know I just flooded you in 20 minutes with a whole bunch of information. So hopefully you found it all insightful and it's motivated you to use TypeScript in your next project, or maybe even in your current React project. That would be awesome. So again, the slides are already available online, visit benmvp.com. I keep sending you there. The slides are there, or you can follow that Bitly link. If you've got questions, feel free to reach out to me on Twitter, and we can chat there. All right, thanks, and I hope you enjoy the rest of the conference.