React and TypeScript Polymorphic Components
This course has been updated! We now recommend you take the React and TypeScript, v2 course.
Transcript from the "Polymorphic Components" Lesson
>> We'll look at this idea of polymorphic components. Cuz buttons are an interesting one. A lot of times we use buttons as they're intended to be used. And then a lot of times, we'll have buttons that we want them to look like buttons. But semantically speaking, they should be hyperlinks, right?
[00:00:24] The designer hands is something where it looks like a button. But it's yeah, it's not to like do an action on the page is to actually bring us to another page. And we might have something where we want the component system you we want our button to either be a button or be a link that looks like a button.
[00:00:39] And we don't want to necessarily have a button and a button as link component if we can avoid it. But you can imagine that, okay if there is a little voice screaming in your head that says, but Steve, won't TypeScript not let me do like dynamic stuff like that anymore.
[00:01:00] And I will argue that is not true, you can have a way in which you might seek to have the ability to find it as a different component. So a lot of times what we'll see is an as properties. So you can have a button and you might have an as, a different HTML element and sub it in, that'll work.
[00:01:19] Also, we've been a little bit, our last button didn't take all the props a button did. So we can actually figure out how to solve for some of these things. But what I would love to do is I'd love to say put button primary, I'd love to say as an a tag.
[00:01:33] And then it should be able to take an href, right? In this little version is not doing all the intellisense. But like this wouldn't work right now cuz you can clearly see that the does not on the button pops. And you can actually see it down there. Let's open it up fully.
[00:01:48] So we wanna say, okay, by default, if you say a button, it should be a button. Otherwise, if they say as some other kind of element, we want to use that instead. And like you can limit it, just using like Wolcott, what are the strings we expect is union type.
[00:02:03] You see something like another place that you might use this and we will use this as an exercise in a second is around the concept of like things that can take some text. You might want to have a text that could be like a paragraph, or possibly a div or a section or an article that you want to be able to do certain things in your component library with.
[00:02:24] But you want to be able to give it the different semantic meanings that it might need. And so this pattern will allow you to do that, as well. So what we need to do is we need to figure out, which parts that we are going to, which parts are going, that we're going to override and have a regular button, and which parts that we want to kind of pass in.
[00:02:49] So we're gonna need a generic here. So we'll say type of button props. We're gonna make a new property here which is gonna be as. And again, we said this before, this is going to be some kind of other HTML element, an anchor tag button, something along those lines.
[00:03:14] And we could use t we I'm going to use e in this case because we know it's going to be it's supposed to be an HTML element. If you really want use t, go use t, and right now TypeScript angry. TypeScript is angry because it's like, I don't know.
[00:03:26] I don't know what, I don't know what he is. So we are in TypeScript is is it's happy again, all right? When you use this type, it's angry somewhere else, right? Because now we didn't pass in e, but we will. We can say okay. We can pass in any type, but we know we don't want any type.
[00:03:45] Remember when we did this with the higher order component before, we're like, hey, yeah. What if you only gave me components that take a user property? Wouldn't that be great? Yeah, yeah, that's cool. So we want E, but we want E to be an HTML element because we're gonna use it as an HTML element.
[00:04:01] So say E extends react element type. And we saw things that extend react element type or when we using our event listeners before the HTML input element, so on so forth. And they all, we'll just have a default to react element type. So this will be our buttons own property.
[00:04:26] So these are kind of the ones of our component systems button so button own props. This is how before we set out like the button props in the primary and secondary button props. And so here we say type ButtonProps is again, it's going to be that we're going to have that idea of E again.
[00:04:45] And we'll talk through this because this is the reason we had that little chat about generics before we jumped into all of this stuff is, if you didn't understand what the angle bracket syntax was, we can talk through it. But having some of that context is definitely helpful as well.
[00:05:00] So we'll have that idea of E cuz extends react element type. And what this is going to be is give me everything in button own props, right? So our buttons are unique special buttons that we're gonna pass in. That's taking primary, secondary destructive and stuff like that. Those are not part of the HTML button spec.
[00:05:21] So take everything that's in our buttons and combine that, With everything that is in the react component around whatever E is, minus the ones that we define. So remember, we did this to the higher order components. The line I'm about to write is exactly what we did with the higher order component before.
[00:05:41] But we're also just combining it with our own. So well met E, and then key button props ButtonOwnProps. All right, one more curly brace or angle brace. Nope. Yeah, one more one way. I caught that one. Let's see. Let's hover over and see what the issues are omit lacks return type any.
[00:06:23] I don't need that, I put that in, there we go. All right, so we've got this new button props. And so it's a little much to look at here. But it is looking at anything that you pointed out as we get excluding children, primary, secondary, destructive, and as, which makes sense.
[00:06:42] So our button pops will be anything that does, but we're gonna pass these in ourselves. Okay, cool, and then now we need to figure out what's going on. So let's just review for a second. We've got a generic called E. We placed a constraint on E that is something that conforms to the shape of an HTML element.
[00:07:04] We take the button on props that we just made, we made a new type that is whatever the element takes. But lets us override it with in this case, children, for instance is the most important one. But it lets us override it with our unique types. So we now know this takes as well.
[00:07:23] And we're going to figure out what E is in this case. And so our button now our button component is also going to say like, hey, this now this is now going to we basically have to pass the rename. Button is going to take some, it's gonna figure out usually for the as what E is it's going to pass it into button props, right?
[00:07:46] Button props is going to then look at the element type and look at our own button props and kind of pass it up and down back through. So from when we define as that will get passed in to button props. We will create a new type that has everything that element does minus the things that we defined so on so forth.
[00:08:07] And TypeScript is going to figure a lot of that out for us. So let's make, let's say the by default button should be buttons, right? That checks out. And so we'll say that take the generic Write element type, and by default, it's going to be the type of our default element.
[00:08:42] All right, so TypeScript was pretty happy with us right now. We do need to do some stuff here. Right now we'll do what we did all this work on the type, and then we still return a button. So that's not incredibly useful. And one of the things in react is that it's, all of our HTML elements are lowercase.
[00:09:04] Are anything would like having react figured out we usually use uppercase letters. So we're just going to kind of rename it real quick. We'll say that a TagName is whatever the as the optional as property was, or is that default element. And here we're just gonna say, TagName, Everything works.
[00:09:29] Now, try to ref here, it's like whoa, whoa, I mean there's a lot to read here, but you can kind of see in there. It's like, you see that ButtonHTML elements, HTML button element. There's no RF on there. But what happens if I say, okay, this button treated as an a tag?
[00:09:52] Look at that dynamically, the element that we're using, I know that E is, now set to an a tag. Get me all of reacts properties of a tag, ref is definitely on there. Definitely support primary as well from our ButtonOwnProps and kind of dynamically create that type based on everything that an anchor tag includes, plus everything that we defined, right?
[00:10:16] And if again, if we remove that it's like whoa, like we default back to button where ref is not supported. And everything about in support is okay as well. Remember that you can see ID didn't get as a problem even though we didn't define ID because button support ID, right?
[00:10:35] So all HTML element support ID. So that was fine. But it dynamically figures out the type based on the information we gave it. So you're an anchor tag, and you've got button on props. I'll support both of those. I'll figure it all out for you. But if you try to use something, just anything in there, I'm going to try to prevent you from making mistakes.
[00:10:54] Because maybe you're refactoring a bunch of buttons into anchor tags. I will help you along the way so you don't have some weird edge case that you don't find until much later.