When we have nested components, we need a way to communicate and send data between them. We know that we can pass data to child components using props, just like we did with the name of the plant. But we don't know yet how to pass data from the child to the parent. Let's figure this out by continuing to work on our example.
So in our plan picker, we want to be able to pick a plant. That is, when I click on it, it should stay highlighted with the green outline. Maybe it's a good idea to add some local data to our CoffeePlan component in order to keep up with whether or not it's selected. I'll import ref from view and then define selected, defaulting it to false.
Then on the div below with the class of plan, let me add the active plan class. Over in the browser, you'll see that all of them now have the clean outline. css file, and we need to conditionally add it whenever selected is true. So let's add another class attribute here, and then we'll say active plan should be applied whenever selected.
Now they all disappear because all of the components have a default selected value of false Now we can say that whenever we click on the plan we want to set selected to the inverse of selected So if it's already selected then deselected if it isn't already selected then select it over in the browser now I can click the single move away from it and yeah that green outline stays However, if I click on any of the other coffee plans, you'll notice I'm now able to select more than one at a time. Maybe that's not what we want for our app. Maybe we only want to be able to select one. In order to accomplish that, we need plan picker to know what the selected plan is.
And since we are keeping up with selected here in coffee plan, we need a way to communicate up the chain from coffee plan to its parent plan picker. Well, we can communicate up the chain of components via custom events. And the way we define custom events is with the define a mince function for view. We can define as many custom events for our components as we'd like.
Let's define one called hi there just for the sake of demonstration. Next, I want to omit this hi there event whenever the the plan is is clicked. So let's extract this logic for for toggling the selected state into a function. We'll call it select plan and we'll define select plan here.
Select plan will still set selected to its inverse. Now that we're inside a script setup, though, we should use dot value. since it's not automatically unwrapped for us like in the template, and then we will emit the hi there event. Now notice I have a red squiggly line under emit.
Right now it says that emit is not defined. The way we access this emit function is by getting the return from define emits. Perfect. All right, now I'm going to open up my browser dev tools and open up the the View DevTools here.
Inside of the View DevTools is this handy timeline that allows us to see all of our component events. So when I click on any of the coffee plans now, whoops, I need to start recording. And if I click on a coffee plan and another coffee plan, sure enough, right here under the road labeled component events, you do see each instance of the event being fired. There are these little green dots here.
If I continue to click around, you see more and more being added. Okay, great. This isn't very useful, however. What we want to do, probably, is define a different event.
Let's call it selected, meaning that the parent component, the plan picker, can listen for the selected event and know whenever we select one of these plans. So now I'll emit selected. and all events can optionally have what's known as a payload. This is data that's sent up along with the event, so the parent can do something with it.
I'm going to pass in the CoffeePlan name so that the parent knows which CoffeePlan was selected. name here, and then we'll have to actually capture that return of definedProps in the props variable. Awesome. All right, so that takes care of actually omitting the event the child component.
Now inside of the parent component in plan picker, we need to listen for the event fire. That's done with V on, just like with a regular event listener on a native element, or we can shorten that to at. Then we give it the name that we want to listen to, in this case selected, and now the payload is available in this special dollar sign. Event keyword, so let's create a new function here to handle this event.
We'll call it handle select Coffee plant and it will take in the event Okay, so that dollar sign event and I'll just shorten it to e for short and then we can call it right down here so handle coffee plant and Pass it the dollar sign event great now This E is actually that payload, right? So it is the coffee plan name. So maybe a better name for this would be Name. Then I'll set some data local to the plan picker called Selected Coffee Plan.
And by default, it will be undefined. But whenever somebody clicks on a selected coffee plan, we'll set it to the payload for that event. Just above all of our coffee plans here. Let me also print out the Selected coffee plan now and give the page a refresh over here for good measure Sure enough when I click on any of the coffee plans now We have the name of the selected coffee plan displaying at the plan picker level.
This is awesome Now our parent knows when a certain component has been selected This is all thanks to our custom events Remember, we've defined a custom event with the define emits function. We have captured the emit function from the return of define emits, which allows us to actually emit a particular event called selected. It has a payload of the coffee name, the coffee plant name. And then in order to catch that event inside of the parent, we listen for at selected.
So we listen for the selected event. and then call some function whenever the event occurs. The $event keyword includes the payload, but we could actually even shorten this just a little bit as it's automatically passed to the function and doesn't have to be specified. So this is the exact same thing as providing the $event.
Over in the browser, this works exactly the same. So our last step here is to um use the selected the the selected coffee plan from the parent to actually determine that class used on each of the coffee plans in order to get the styling that we're looking for where the green only applies to that one selected coffee plant so since we're keeping up with the selected coffee plant at the parent level it really makes sense for us to pass in which one is selected to the coffee plan component This will be a Boolean prop. So we'll just say, is the name of the plan the same as the selected coffee plan? Is the name of the currently iterated over plan the same as the selected coffee plan?
If it is, then selected is true. So when I come over here to coffee plan and then add support for that selected prop, we'll give it a type of Boolean. and it will default to false. This means we no longer have to keep up with the selected data locally.
Notice, in fact, I'm getting a red squiggly line saying duplicate key selected. In other words, selectives are already available as a prop. I don't need to define it as local data as well. So we'll remove that.
And I no longer have to set the value of that anymore inside of our select plan function. Instead, that's happening at the parent level. when we emit the event. I will give the page a refresh here.
And now when I click on the single, it has the green outline around it. When I click on the curious, only it has the green outline around it and the single is no longer selected. And this works perfectly. By the way, it's worth noting that the custom event probably wasn't necessary for this situation.
We could have actually put the at click event handler right here in the parent on the coffee plan component so something like this we could say at click then handle selected coffee plan and that means that the the coffee plan component wouldn't necessarily have to define any custom function instead the click would just fall through so i'll remove the click here and really now the click is is this right here right so that click falls through to this root div element and whenever we click on a coffee plan the parent knows what to do with it. If I refresh the page click around oh that's not quite right because now handle select coffee plan is actually the html element and not that custom payload that we were sending up before. So here let's just pass the name of the plan that we have available right here. And refresh, click, we've got the single, it's highlighted in green.
We do the curious, only it's highlighted in green and the single is not any longer. So this works exactly the same. However, I needed to demonstrate to you how custom events work and that's exactly what I was able to do. Moral of the story is sometimes you can use regular native event listeners.
that will be applied to the root element of a child component. Other times though, it will make even more sense to define a custom event and use define emits as we did during the video. When we have nested components, we need a way to communicate and send data between them. We know that we can pass data to child components using props, just like we did with the name of the plant.
But we don't know yet how to pass data from the child to the parent. Let's figure this out by continuing to work on our example. So in our plan picker, we want to be able to pick a plant. That is, when I click on it, it should stay highlighted with the green outline.
Maybe it's a good idea to add some local data to our CoffeePlan component in order to keep up with whether or not it's selected. I'll import ref from view and then define selected, defaulting it to false. Then on the div below with the class of plan, let me add the active plan class. Over in the browser, you'll see that all of them now have the clean outline.
css file, and we need to conditionally add it whenever selected is true. So let's add another class attribute here, and then we'll say active plan should be applied whenever selected. Now they all disappear because all of the components have a default selected value of false Now we can say that whenever we click on the plan we want to set selected to the inverse of selected So if it's already selected then deselected if it isn't already selected then select it over in the browser now I can click the single move away from it and yeah that green outline stays However, if I click on any of the other coffee plans, you'll notice I'm now able to select more than one at a time. Maybe that's not what we want for our app.
Maybe we only want to be able to select one. In order to accomplish that, we need plan picker to know what the selected plan is. And since we are keeping up with selected here in coffee plan, we need a way to communicate up the chain from coffee plan to its parent plan picker. Well, we can communicate up the chain of components via custom events.
And the way we define custom events is with the define a mince function for view. We can define as many custom events for our components as we'd like. Let's define one called hi there just for the sake of demonstration. Next, I want to omit this hi there event whenever the the plan is is clicked.
So let's extract this logic for for toggling the selected state into a function. We'll call it select plan and we'll define select plan here. Select plan will still set selected to its inverse. Now that we're inside a script setup, though, we should use dot value.
since it's not automatically unwrapped for us like in the template, and then we will emit the hi there event. Now notice I have a red squiggly line under emit. Right now it says that emit is not defined. The way we access this emit function is by getting the return from define emits.
Perfect. All right, now I'm going to open up my browser dev tools and open up the the View DevTools here. Inside of the View DevTools is this handy timeline that allows us to see all of our component events. So when I click on any of the coffee plans now, whoops, I need to start recording.
And if I click on a coffee plan and another coffee plan, sure enough, right here under the road labeled component events, you do see each instance of the event being fired. There are these little green dots here. If I continue to click around, you see more and more being added. Okay, great.
This isn't very useful, however. What we want to do, probably, is define a different event. Let's call it selected, meaning that the parent component, the plan picker, can listen for the selected event and know whenever we select one of these plans. So now I'll emit selected.
and all events can optionally have what's known as a payload. This is data that's sent up along with the event, so the parent can do something with it. I'm going to pass in the CoffeePlan name so that the parent knows which CoffeePlan was selected. name here, and then we'll have to actually capture that return of definedProps in the props variable.
Awesome. All right, so that takes care of actually omitting the event the child component. Now inside of the parent component in plan picker, we need to listen for the event fire. That's done with V on, just like with a regular event listener on a native element, or we can shorten that to at.
Then we give it the name that we want to listen to, in this case selected, and now the payload is available in this special dollar sign. Event keyword, so let's create a new function here to handle this event. We'll call it handle select Coffee plant and it will take in the event Okay, so that dollar sign event and I'll just shorten it to e for short and then we can call it right down here so handle coffee plant and Pass it the dollar sign event great now This E is actually that payload, right? So it is the coffee plan name.
So maybe a better name for this would be Name. Then I'll set some data local to the plan picker called Selected Coffee Plan. And by default, it will be undefined. But whenever somebody clicks on a selected coffee plan, we'll set it to the payload for that event.
Just above all of our coffee plans here. Let me also print out the Selected coffee plan now and give the page a refresh over here for good measure Sure enough when I click on any of the coffee plans now We have the name of the selected coffee plan displaying at the plan picker level. This is awesome Now our parent knows when a certain component has been selected This is all thanks to our custom events Remember, we've defined a custom event with the define emits function. We have captured the emit function from the return of define emits, which allows us to actually emit a particular event called selected.
It has a payload of the coffee name, the coffee plant name. And then in order to catch that event inside of the parent, we listen for at selected. So we listen for the selected event. and then call some function whenever the event occurs.
The $event keyword includes the payload, but we could actually even shorten this just a little bit as it's automatically passed to the function and doesn't have to be specified. So this is the exact same thing as providing the $event. Over in the browser, this works exactly the same. So our last step here is to um use the selected the the selected coffee plan from the parent to actually determine that class used on each of the coffee plans in order to get the styling that we're looking for where the green only applies to that one selected coffee plant so since we're keeping up with the selected coffee plant at the parent level it really makes sense for us to pass in which one is selected to the coffee plan component This will be a Boolean prop.
So we'll just say, is the name of the plan the same as the selected coffee plan? Is the name of the currently iterated over plan the same as the selected coffee plan? If it is, then selected is true. So when I come over here to coffee plan and then add support for that selected prop, we'll give it a type of Boolean.
and it will default to false. This means we no longer have to keep up with the selected data locally. Notice, in fact, I'm getting a red squiggly line saying duplicate key selected. In other words, selectives are already available as a prop.
I don't need to define it as local data as well. So we'll remove that. And I no longer have to set the value of that anymore inside of our select plan function. Instead, that's happening at the parent level.
when we emit the event. I will give the page a refresh here. And now when I click on the single, it has the green outline around it. When I click on the curious, only it has the green outline around it and the single is no longer selected.
And this works perfectly. By the way, it's worth noting that the custom event probably wasn't necessary for this situation. We could have actually put the at click event handler right here in the parent on the coffee plan component so something like this we could say at click then handle selected coffee plan and that means that the the coffee plan component wouldn't necessarily have to define any custom function instead the click would just fall through so i'll remove the click here and really now the click is is this right here right so that click falls through to this root div element and whenever we click on a coffee plan the parent knows what to do with it. If I refresh the page click around oh that's not quite right because now handle select coffee plan is actually the html element and not that custom payload that we were sending up before.
So here let's just pass the name of the plan that we have available right here. And refresh, click, we've got the single, it's highlighted in green. We do the curious, only it's highlighted in green and the single is not any longer. So this works exactly the same.
However, I needed to demonstrate to you how custom events work and that's exactly what I was able to do. Moral of the story is sometimes you can use regular native event listeners. that will be applied to the root element of a child component. Other times though, it will make even more sense to define a custom event and use define emits as we did during the video.