- Inline statement handler: Handle multiple arguments
- Emit event from functional components
- Pass all props to the child component
- Watch child properties changes from parent
- Listening for child event in the router-view
- Vue components lifecycle hooks don't run in the order you think they run
- How to know if a child component is mounted
- How to know if a dynamically vuex registered module is registered
Inline statement handler: Handle multiple arguments
Short Explanation:
In an inline statement handler besides the $event
special variable we have access to another special variable: arguments
. $event
will have access only to the first argument, whilst the arguments
will have access to all arguments.
Detailed Explanation:
In an inline statement handler we already know that we have access to the special $event
variable.
So if a child components emits an event with a parameter, we have access to it by using $event
:
Parent component
<template>
// ...
<Child @event="someVariable = $event" />
// ...
</template>
Child component
export default {
//...
emitEvent () {
this.$emit('event', 'a simple text')
}
//...
}
That works really well when the child component, is a component that we have access to, since we can be sure we pass only one parameter.
But how if we use a third-party component/library (e.g. dropzone.js) which passes many arguments up via event ??
The $event
will have access only to the first argument. See this codesandbox example which illustrates that $event
will catch only the first argument. (Click the button and see the console)
In that case, instead of $event
we can use arguments
and all arguments will be accessible.
So in the codesandbox above, in order to make it work, we have to change the line 4 in the Child.vue
from:
<GrandChild @event="$emit('event', $event)"/>
to:
<GrandChild @event="$emit('event', arguments)"/>
Emit event from functional components
Short Explanation:
Using functional components, means that we don't have access to this
context. Thus, we cannot do this.$emit()
. But... we have access to listeners
so we can do <button @click="listeners.clicked"></button>
Detailed Explanation:
Functional components are stateless (no reactive data) and instanceless (no this context). But functional components have access to some properties such as props
, children
etc and most importantly (for this case), listeners
.
According to Vue docs:
listeners: An object containing parent-registered event listeners. This is an alias to data.on
That means that we can emit event from functional components. Wiii :P
Simple example:
<template functional>
<button @click="listeners['custom-event']('message from child')">
Button from child
</button>
</template>
Working example (click the button and open the console)
How if we want to emit an event from functional component with render functions? Can we do that? Of course!
Simple example:
export default {
functional: true,
render(createElement, { listeners }) {
return createElement(
"button",
{
on: {
click: event => {
const emit_event = listeners.event_from_child;
emit_event("Hello World!Is this the message we excpected? :/");
}
}
},
"Pass event to parent"
);
}
};
Working example
Someone may wonder if we can use .sync
Modifier using this approach.
The answer ? Of course!
<button @click="listeners['update:message']('some text')">Click me</button>
Pass all props to the child component
Say that we have a component which receives props
and we want to pass all those props to a child component. To achieve that, we can do:
<ChildComponent v-bind="$props" />
Taking advantage of v-bind
we can also have an object like:
data: () =>({
obj: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
})
And pass firstName
, lastName
, age
as props
to a child component, like:
<Child v-bind="obj" />
Watch child properties changes from parent
You may wonder why to do that and you are right! That's a bad practice. But sometimes you use a third-party component and you want to watch their properties to fit your needs.
A long time ago, in a project we were using a date picker and we wanted to be able to detect when the popup was visible. Unfortunately there was no option to do that.I discovered that the date picker was using a popupVisible
reactive property but it wasn't exposed by the library. So I had to somehow watch this property from my component.
The below code is NOT able to detect changes:
watch: {
'$refs.datePicker.popupVisible': {
handler (new_value) {
console.log('value changed')
},
deep: true
}
}
In order to detect changes of a child component's property you should do:
mounted() {
this.$watch(
"$refs.picker.popupVisible",
(new_value, old_value) => {
//execute your code here
}
);
}
Working example:
Learn more about vm.$watch
Listening for child event in the router view
Most of you, should already know that since it is straightforward, but I've been asked many times about the question below.
Say you have a component which has nested routes:
<template>
//...
<router-view></router-view>
//...
</template>
And you have a nested route as follow:
<template>
//...
<button @click="$emit('event')"></button>
//...
</template>
So the component corresponding to a nested route, emits an event. The question is : how to listen to that event ?
The simple answer demonstrated with code:
<template>
//...
<router-view @event="callAMethod"></router-view>
//...
</template>
Exactly! We listen to that event in the router-view
component
Vue components lifecycle hooks don't run in the order you think they run
Say you have 2 pages. Home and About.
When switching from Home to About, the created
hook of the About component will run before the beforeDestroy
hook of Home component. (take a moment here)
Weird?? Try switching routes in the working example below by seeing the console.
As solution (idk if it's the best solution though) you can use transition-mode out-in
<transition mode="out-in">
<router-view></router-view>
</transition>
How to know if a child component is mounted ?
This one of my favorite tips I've read here (Vue Dose)
Say you have a child component and you want to do something when a hook of the child component is executed. You can do:
<Child @hook:created="doSomething" />
So the question to How to know when a child component mounted is:
<Child @hook:mounted="componentMountedDoSomething" />
How to know if a dynamically vuex registered module is registered ?
With the power of dynamically register/unregister vuex modules we can improve the performance a lot.
I recommend you to read a very useful article: Performance optimization: Lazy load vuex modules
We can register a vuex module:
this.$store.registerModule('my-module', MyModule)
And unregister it:
this.$store.unregisterModule('my-module')
To know if a module is already registered:
if (Object.keys(this.$store._modules.root._children).includes('my-module')) {
// module is registered
}
My time is very limited but I am glad that I found some time to write this article (it took me some time though). I hope it helped and you enjoyed reading it.
I would be very happy if you leave a comment providing your solution. For example regarding the last "secret" you can use vuex store to indicate that a module is registered or not.
This is my first article on DEV community. Thank you for reading.
Top comments (10)
Really great tips! Thanks for sharing!
Hereโs my little tip, how to add multiple listeners to a functional component.
Thanks for the comment Andrei. Is that equal to:
?
why is that
listeners
instead of$listeners
?in functional components, it's
listeners
. DocsDoesnโt seem to work. ๐ค
What doesn't seem to work?
Your snippet. Thereโs a likelihood of me doing something wrong though. Iโm rendering my component using a render function.
Since we are talking about functional components the full code snippet should be:
Iโll give it another try tomorrow and Iโll let you know how it went!
In this example:
Don't forget to add condition to check, if emit actually requested from a functional component:
if (emit_event) emit_event("Hello World!Is this the message we excpected? :/");
Otherwise, if you won't listen to that emitted event, then you'll get an error.