If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOur search bar is working really well! We can type in it and it keeps things synchronized with the searchTerm
data. We now need to communicate when the searchTerm
changed up to catalog so that it can know to filter the product list.
We might think that, since Catalog
needs to know the searchTerm
data, we should move it up to Catalog
then pass it back into search-bar
as a prop. We did this in a few cases already: we moved a piece of data up the tree to make it accessible to more components.
We could do that... but I won't. Trust me for now - I'll explain why soon.
But regardless of where the data lives, what we need to do in Catalog
is update the product list when the search term changes. In other words, we need to perform an action when an event happens. So for our first order of business, whenever we update the search box, we need the search-bar
component to say "Yo! I changed!" and for it to pass us the new search term.
In search-bar
, this specifically means that on the input
event of the search box, we need to emit a custom event. To do that, add @input=""
and this time, set it to a method name: onInput
.
<template> | |
<div> | |
<input | |
... lines 4 - 7 | |
@input="onInput" | |
> | |
</div> | |
</template> | |
... lines 12 - 28 |
As we talked about earlier, when you have v-model
, one of the things it does behind the scenes is add its own @input
which sets the searchTerm
data to the input's value. In this situation, because we need to do something else on input, we're adding a second @input
. The original one that's set by v-model
is still going to update the searchTerm
data. Then, after it finishes, our custom code will run, which is pretty cool!
Below, add methods
and onInput()
. Inside, emit a custom event with this.$emit()
. Let's call it search-products
. If we intended to re-use this component as a generic search box around the site, we could call it just search
.
And it turns out that $emit
has an optional second argument, which can hold any data that we want to include on the event object. That's good news! Because, in addition to just saying that the search-products
event has happened, we also need to pass what the searchTerm
is. Do that with term: this.searchTerm
.
... lines 1 - 12 | |
<script> | |
export default { | |
... lines 15 - 20 | |
methods: { | |
onInput() { | |
this.$emit('search-products', { term: this.searchTerm }); | |
}, | |
}, | |
}; | |
</script> |
Before we touch anything else, move over to the browser. On the Vue Dev Tools, click on SearchBar
. Actually, click on the Events tab.
Now... type! Yes! We see one event for each change that we made to the text! Check the last event: it has a payload
property that, if we explore, has the term on it. Awesome! We'll see how to use that in a few minutes.
Back to Catalog
! There are two ways to filter the products. The first and easiest is to just take the products
array and filter it using JavaScript. The second is to do a server-side search. That's much more powerful and we will try that next. But to start, let's keep things simple.
When the searchTerm
changes, we don't actually want to change the products
array because we don't want to lose that information: if the user cleared out the search box, we will need to show the original list. What we really need to do is keep track of the searchTerm
and use it to compute a new array of filtered products.
Add a new searchTerm
data set to empty quotes.
... lines 1 - 32 | |
export default { | |
... lines 34 - 50 | |
data() { | |
return { | |
... line 53 | |
searchTerm: '', | |
... lines 55 - 56 | |
}; | |
}, | |
... lines 59 - 74 | |
}; | |
... lines 76 - 77 |
Now you might be yelling:
Ryan! You dummy! You just duplicated the
searchTerm
data! You havesearchTerm
insearch-bar
and you also havesearchTerm
inCatalog
! You said never to do that!!!
That is an excellent point - even the dummy part, a lot of the time. But, I'm doing this on purpose. The searchTerm
inside of search-bar
is really an internal piece of data. It exists just to help that component do its job. Anyone who uses this component doesn't really need to know or care that it exists. All they care about is that this component emits an event when the search changes and passes us the new term
.
Then, in Catalog
, in order to do our work here, it just so happens that we also need to store the search term as data.
Now, we could just put the searchTerm
data in Catalog
and pass it to search-bar
as a prop. But there are two reasons why I won't do that. First, because I don't have to: Catalog
will never need to change the searchTerm
. So because the only place it will ever change is search-bar
, the two pieces of data won't get out-of-sync. The second reason is that, in a few minutes, we're going to add debouncing, which basically means that the searchTerm
inside search-bar
will update immediately, but that component will wait before it emits the event. It's subtle, but in that situation, the two search terms will not always match each other.
If this is confusing... don't worry. There's not a wrong way to do things: if you just put searchTerm
in Catalog
and passed it as a prop to search-bar
, that might be a bit unnecessary, but it would work great! If you added debouncing later, you might then realize that you need two separate pieces of data. I'm planning ahead, but it's always ok to choose a path, move forward, and let the design figure itself out naturally.
Anyways, we're sort of half way done. Next, we need to listen to the custom search-products
event, update the searchTerm
data in Catalog
and use that to print a filtered list of products.
// package.json
{
"devDependencies": {
"@symfony/webpack-encore": "^0.30.0", // 0.30.2
"axios": "^0.19.2", // 0.19.2
"bootstrap": "^4.4.1", // 4.5.0
"core-js": "^3.0.0", // 3.6.5
"eslint": "^6.7.2", // 6.8.0
"eslint-config-airbnb-base": "^14.0.0", // 14.1.0
"eslint-plugin-import": "^2.19.1", // 2.20.2
"eslint-plugin-vue": "^6.0.1", // 6.2.2
"regenerator-runtime": "^0.13.2", // 0.13.5
"sass": "^1.29.0", // 1.29.0
"sass-loader": "^8.0.0", // 8.0.2
"vue": "^2.6.11", // 2.6.11
"vue-loader": "^15.9.1", // 15.9.2
"vue-template-compiler": "^2.6.11", // 2.6.11
"webpack-notifier": "^1.6.0" // 1.8.0
}
}
Hi there,
If you can't see the events, make sure you activate the "recording" option.