Nuxt 3 Fundamentals

Components & Slots Exercise

Ben Hong

Ben Hong

Nuxt 3 Fundamentals

Check out a free preview of the full Nuxt 3 Fundamentals course

The "Components & Slots Exercise" Lesson is part of the full, Nuxt 3 Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:

Students are instructed to refactor the PhotoGallery and TodoViewer components to use a base component and have the content passed in through a slot. To begin the exercise, check out the 04-exercise branch. The solution can be found on the 04-finish branch.


Transcript from the "Components & Slots Exercise" Lesson

>> So now, that you have all that information, I have one more thing to share with you actually. If for instance, we decide you know what, we are not gonna define a custom metrics. We're not gonna worry about it. You can actually define default content in a slot by making the slot, opening up the element and just dropping in whatever you want to be the default.

And just like that, you'll see it refers to that. But the moment the parent decides to override it, that's fine. It can rearrange do whatever it wants, but now you've configured a default slot value that will automatically be populated, and you still also provide some configuration options. So just for the sake of completeness, I will just make a remaining item just as another prop.

Because theoretically, if someone's trying to configure this, they'll need both data properties. So for the sake of completeness, we have all of that. Your job once again, we have the photo gallery and the TodoViewer, you will see lots of similarities. I want you to create a base component that will then be easily taken configuration or however you decide to design it.

Taking options that will allow you to basically share both so that there's less code like duplication happening across the components. Okay, what we're gonna take a look at first is let's go ahead and just look at these two things side by side. And this is, I'm gonna give you a live refactoring of what I would kind of think of when I'm looking at these two.

So let me split that window and okay, great. So did you do? Okay, great. Okay, so when we're taking a look at this, we know for a fact that both of them for example, we see here, both of them have a ref, that's an array. We know that we're gonna have a list of something.

So that's already common. We know for a fact that, let's see, there's this function here that's repeated. So that's something that should certainly be abstracted away, and then what else do we know? We know that actually, for the most part, we wanna keep most of the HTML actually pretty similar, we don't want them to really vary all that much.

I know it wasn't immediately apparent based on the way we've been playing around with styles. But for the sake of a base component, we should assume that they more or less should look the same. So whenever I start to refactor, the first thing I think of is basically I prefix, I have a set of components and a prefix called base whatever.

So the reason for this is because in a lot of, I know component namings schematics, people tend to veer towards one word names so like button, link that stuff. And yes it's really short, but as we know that conflicts with HTML specs really easily. So rather than confused people on, wait is this Vue, is this HTML?

What is this? And then if someone in the future has a class with it later, for me, it's simplest is to go like for example, we'll call it base display because this is just a display for data is basically what we're doing. So basedisplay.Vue, this is kind of where I start.

So I'm gonna go ahead and open up my template and I see there was a great question earlier. Do you wanna actually go ahead and ask about this?
>> Yeah, I seen in a couple other cases rather than script templates style, I'd seen the sequence template script style I believe, I was wondering whether that's a compiler thing and ergonomics thing.

>> Got it. So this is an excellent question and one that I actually do get a lot. And so the first thing to know is that the order blocks is completely irrelevant to how it's being compiled. What's important to know is that over time you'll notice that and especially in Vue 2.0, we did template script style, that was actually the convention for the longest time.

And over time, I think what we started to notice is that When it came to the ergonomics of doing development, what do we intend to see? Well, we have some static, some HTML to top, we have a long body of JavaScript, and then we have some styles at the bottom.

But when you think about it, when you're working on styling your component, what are you working with? Template and CSS. But if you have all this JavaScript in the middle, then you have to like scroll all the way down. And don't get me wrong, I know you can collapse the script block and stuff to minimize that stuff, but then vice versa though.

If you're only working on your script block and your HTML block, well that is right next to each other. And so basically what we realized is the template is kind of the intermediary between both blocks. It's really rare that you're modifying JavaScript so that you can modify CSS, usually it's that tie in.

So just to provide a little bit of developer experience ergonomics basically the template has basically a lot more we have been adopting this where the template is in the middle. So that the scrolling is just a little bit less than, you have less to collapse. Again, total preferencing, but that's where the rationale behind the convention that you're starting to see now nowadays, so.

Okay, so what are we talking about? We talked about a generic list item, we need that. And we're gonna have some generic layout stuff, and then we're in template though, so I need this one, generic template stuff, all right? Let's do this. So first thing first. I think that TodoViewer at this point at least on my code base is the most mature one.

So let me go ahead and actually make sure I switch over to the four finished branch, so we don't commit on the wrong one. And the first thing first is I'm just gonna go ahead and actually just copy the HTML, so we don't just replicate all this. All right, so the generic template here.

All right, so what can we say with this? So what I'm gonna do now is I'm gonna stop doing the side by side because it's really hard on this particular resume. We know that the hero has been abstracted. This is great The title, this has been genericized. That's good.

Okay, this fetch data, this is a little bit. Okay, this is a question mark here. So this is like a Todo item, and then we have slots for the metrics. And we're not gonna do a default for now because right now we really don't know what kind of data we're getting.

So who are we to really say, what that's going to be. So let's keep that, let's remove the props that I showed you earlier. And then finally, yes, we are gonna have a list of items, but we don't necessarily know what's gonna go inside of every item. So I think for the purposes of this, the easiest thing would probably be to say, in this case, I might actually make this part of slot.

That's kind of what I'm actually leaning towards. So for now, I think I'm just gonna go ahead and just make this a slot, and I'll call these items, that's what this slot will be. So we'll delete that and just like that, okay, this is now this looks pretty generic.

The only thing we need to do though is now this fetch thing. What is this thing? Well, so we know that we're basically fetching some kind of data. So for now, let's just call it fetch item list. That's like a generic enough name. And so the first thing we wanna do is let's go and just define that since that's the first thing we kind of abstracted out.

And so what we know is we're going to copy over this fetch to-do list right here. And in fact, I realize I'm gonna do that function thing that I was talking about before, because I think it's nice. All right, so we have this, and we're gonna make this generic, so this is fetch item list, and what we do know is that here we have the ability then to say, okay, we know what the type is.

So what we could really do is we could have a prop of the type that we're actually looking for because this JSON placeholder for now is a consistent URL. So I'm going to do here is I'm going to go ahead and switch to ES6 template literal, and we're going to do props.itemTYpe.

So this will genericized what this is. Now granted we still have this thing to do right here this thing is still a problem, because this right here is calling a to-do list our value which we won't actually have. All right, so what do we do next? Let's at least figure this part out.

Well, we need to define some props. So I do const. So actually let me show this as it makes more sense. Now as I mentioned before, we can define props using the define props method, which I'm just gonna go ahead and import from Vue. And then inside of here, we can then say the item type is going to be of type string.

And this time, let's not make a generic assumption. So we're just gonna make it required true. Now the thing is, though, because we're in vanilla JavaScript land, this doesn't have a reference point. It's just a function that runs and defines some props. But we wanna refer to it here as props.itemType.

And so the way we do that is we actually assign the variable to it, props, and that's what allows you to access the values inside of the props. Because without this, it's just a generic function that just runs, and it's part of the component definition. But since we want to use it, we actually need to declare it inside of our script setup block.

Okay, so that's two pieces of the puzzle solved. The next thing we need to actually be able to do though, is we need a generic list item, right? Because this thing is definitely not gonna be todoList. So let's go ahead and do a const itemList. And this itemList, we'll use a ref, and this will be an empty list.

So then we'll say ref here, just like that. And then now, we can switch todoList to itemList. And now, we can save that, and this is starting to come together a little bit. So okay, the next thing I find that's easiest to do then, so let's see, that's done, that's done, that's done.

Let's actually see how it works in practice. So what we're gonna do here is we're gonna open up our TodoViewer, and what I'm gonna do here is, let's import the base component first. So import BaseDisplay from, and then it'll just be BaseDisplay, cuz we're in the same directory as our components right now, so that's what's happening.

So now that we've sort that out, let's go ahead and use it, shall we? So we have BaseDisplay, and then we're gonna have a couple of things we'll need it to do. So what kind of props does it have.? Well, if we look at the definition, we'll see that we have, okay, we have a titleProps.

So let's go ahead and give that title right now of TodoViewer. Okay, and then what else can we do? Well, we need to actually tell it, wait, BaseDisplay, yeah, BaseDisplay. Why did it define it that way? This is where it's being called, my mistake. Okay, so BaseDisplay here actually is taking a props of itemType, my mistake.

So we go, itemType, and then we wanna make this the todos. So that's what we're expecting in the list. So once we save that, we can now basically, okay, so that will update that. And what we wanna do now is, let's go ahead and expose the value that we're getting of this itemList.

We're gonna expose it back to our parent to actually take a look. So I'm gonna say that this will be an itemList prop that will get itemList, just like that. So inside of here, let me go ahead, and I'm just gonna comment all this out so that this doesn't get distracting.

Inside of our template block where we're having the v-slot items, we're gonna get our slotProps, right? And so now I'm just gonna spell it out explicitly so people can follow. And then let's go ahead and just render out the slotProps.itemList. Okay, so if we go inside of our app now and refresh, all right, so we see here, Fetch Data, okay?

We see that actually something is returning. Okay, so this is pretty good, that's fairly promising. The only thing though is that there's no heading being shown. And that's because we're looking for a title here that we actually haven't defined. So let's go and add that real quick here, to title, type String, and once again, let's make this also required.

And then once we do that, now we can actually add the title here and do Todo Viewer, just like that. And if we refresh, there you go, Todo Viewer is there. We see the list of data, okay, pretty good so far. Let's go ahead and just see how far this gets us with the photo gallery.

So I'm gonna go over to the photo gallery. And let's go ahead and comment all this out. And I'll just copy this component that we're using in Todo Viewer as a starting point. Oop, here we go. And then instead of Todo Viewer, it is the Photo Gallery. And the type is gonna be photos.

And then, yep, everything else, I think, stays the same for now. So we save that and refresh. All right, one, we're already seeing some standardization. There you go, now we see the list, now we see the other list. Okay, this is looking a lot better. But we have a small problem that is not immediately obvious, which is that if we take a look, let's say, okay, so actually metrics.

When we talked about metrics, let's not forget that Todo Viewer and Photo Gallery have two very different ideas of what metrics they wanna track are. So there's this bit of a problem of, well, I can expose the items via slotProps, and I guess I could run some calculations literally in line here, but it feels really ugly.

It feels weird because the concern is really, it should really be sitting the logic in Todo Viewer. It doesn't make sense to put that logic inside a BaseDisplay, cuz BaseDisplay is supposed to be generic. So how the heck are we supposed to solve this problem? So we have this itemType, we have this title.

And the thing is that what if you could actually have your parent watch a value, like a v-model, from your child that then automatically syncs it, almost like a two way thing. And so in other words, what I'm saying is, so if you think about it, when we talk about input, right?

So we've seen before, so let's use a template for Hero for now, just to give a place to show how this works. So if we have a template for v-slot:hero, and we have an input type of text, and we do a v-model, and I'll just call it textModel for now.

And then let me create a ref here real quick, const textModel = ref( ). And then all we gotta do is render that here, textModel. As you can see, as I'm typing it, it updates, right? But what we want is we want that at a component level. So in other words, we wanna be able to say, hey look, I want you to watch this property here, itemList, that you generically have.

But then you're gonna watch it at the parent level. And it's gonna map. And so I'm just gonna walk through the solution and then hopefully it clicks a little bit. So the thing about v-model that we talked about before is that it basically watches, so if we break apart of v-model, I just need to demo this, so that people are aware.

v-model actually consists, here we go, okay. v-model is just syntactic sugar for breaking apart two things, one, what value it is. So in this case, it was textModel. And then it was saying, hey, on this particular thing, set textModel.value = That's at a high level what's happening.

It's doing two things for us at once. And so since this is a common pattern, we abstracted it into v-model. But again, at the end of the day, we're not doing any black magic, this is just how HTML works. We've just combined it into one command. Okay, so what we ideally would like to do is say, hey, when we look at a component, watch a specific value, and then assign that value to something that I care about at the parent level.

So the way this works, is like this. So I'm gonna delete all this, we don't need this right now. All right, what do we care about the inside of our BaseDisplay? We care about this itemList, right? And so what we ultimately wanna do is we wanna actually be able to say, hey, we wanna tell whenever something like a change happens.

So in this case, right? So earlier in the text, we said that when the input event happens, that's when the change occurs. But for us, where's our point of change in this function? It is right here. After all this is done, something has changed so tell the parent component it's changed.

So how do we do that? So for those who are new to Vue 3, we also have this thing called emits. And it's similar to props in that we get to define a series of events, custom events essentially that we're going to be emitting from the child component.

And so this might be a bit odd for those especially coming from a React background, cuz I think most React developers are used to passing functions down from their parents to the children to do stuff. But if you think about how the web naturally works, event bubbling is actually how the web is built, right?

Because it happens that somewhere, like a child component has an event happen, it bubbles it up to the parent. The parent decides what to do with that information rather than it sort of being unidirectional. So what we do is similar to our props, we can have a define emits helper function.

And so this allows us to define a custom event. So here, it's just basically I'm gonna take an array of what it's called. And so the thing about the the v-model in Vue 3 is it got an update because the v-model can now take a custom argument and I'll show you what that means in just a sec.

So when we think of v-model, I think the way v-model breaks down is that it emits an event that's called update, I think model value by default. And then the thing that it's watching is called model value underneath the hood, that's how this thing is paired up. And so what this means now is that if we have a prop of itemList, it's of type array which I'll default to an array like this.

And so this is just, you need a function to return an array for reactive reason. Just know this is a standard that's done and there's a reason why it's not just like this. It basically it needs to generate a new array every time this base display is being used.

So here's what's the cool part. You can actually update, emit an event called update itemList, so these two things are matching. And so what happens is you can actually, if I remember, if I'm getting this right, we can emit basically when the change happens. We will actually emit a update itemList event and the payload we're passing is that JSON object.

Now hang on with me for a sec, I know you're still probably trying to wrap your brain around it. So now inside of Todo Viewer, I'm gonna listen to an event, a v-model specifically on itemList. And on itemList, I wanted to map to my ref todoList like this.

So to prove that this works, I'm gonna go ahead and do, yes, do the template again. V-slot hero, and this time, we're going to pre the todoList. And then we're just gonna ignore this for now. Oops, sorry, I haven't add symbol. I think something's funky and this should be it.

Okay, we refresh that again. Good, it works. [LAUGH] Okay, let's walk this through again now that we've seen it all together. The thing is, is that what we learn in the Vue 2 watching people build enterprise stuff is that oftentimes when you're looking at a component, you need it to basically look at a prop.

And people were doing weird things that either mutate props or then throw it up the chain. And so we said, wait a second, what if we actually allow people to emit an event that would say, hey, there's this item that you care about in this child component. And then you can watch it like a v-model and bind it to the parent data that you care about.

And this is why this itemList can be bound directly to the todoList. And now, todoList gets all the data it's supposed to have and you can now actually run all your computed properly. So that when we're inside of the metrics, for example, let's go to v-slot, the metrics.

We can actually just output the completedItems.length here and then the remainingItems.length remaining just like that. And then let me actually delete this for now, there you go. And then when you fetch it, now the logic is where you want it to be but you have created a generic way to do all the things that you happen to care about.

And so I know this was a bit of a maze, twisty turn. But again, this code will be committed using a view there for you to analyze. But passing the model arguments in combination with these base components and then you combine them at slots. You get this really interesting set of combinations that you can now use and work with so that you're still basically running things through what natural DOM, how things would progress.

But you get the syntactical sugar of what the framers provide you which is abstracting that complexity away and then just making it work. And so now for example, here in our photoGallery, we can just say that, hey, we want to have or we want to v-model what specifically the itemList that comes from the child.

And we're gonna attach it to our photoGallery list that we have. And so now, we can just simply, I can copy this ul here. Although wait, I think the items no, the items is actually wrapped in something else. So actually, let's just do the li's like this. We're not gonna use a slotProps like that.

And so now we go inside of our photoGallery and such, you'll see everything renders as expected.

Learn Straight from the Experts Who Shape the Modern Web

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