My emit doesnt work, I cant send any data to parent - vue.js

This is my code - child:
<a-camp :camp="camp.name" #click="$emit('camp-selected', camp)" />
export default {
emits: ['camp-selected'],
...
parent:
<a-camp-select #camp-selected="campSelected(camp)"></a-camp-select>
When I try emit without -camp- parameter it works, I can console log test, but when I add -camp-, I cant get it to parent, I also try console log camp in child component and it works. So there is problem somewhere in the middle. Any hints?

You should not mention the parameter when you set the handler for the emitted event #camp-selected="campSelected(camp)":
<a-camp-select #camp-selected="campSelected">
you've to mention it inside the method definition:
methods:{
campSelected(camp){
//use camp here
}
}

Related

Vue3 child component does not recreating, why?

I have made some sandbox code of my problem here:
https://codesandbox.io/s/clever-zeh-kdff1z
<template>
<div v-if="started">
<HelloWorld :msg="msg" #exit="exit" #remake="remake" />
</div>
<button v-if="!started" #click="started = !started">start</button>
</template>
<script>
import HelloWorldVue from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld: HelloWorldVue,
},
data() {
return {
started: false,
msg: "Hello Vue 3 in CodeSandbox!",
};
},
methods: {
exit() {
this.started = false;
},
remake() {
this.msg = this.msg + 1;
//this code should recreate our child but...
this.exit();
this.started = true;
// setTimeout(() => {
// this.started = true;
// });
},
},
};
</script>
So! We have 2 components parent and child. The idea is simple - we have a flag variable in our parent. We have a v-if statement for this - hide / show an element depend on the flag value "false" or "true". After we toggle the flag - the child component should be recreated. This is the idea. Simple.
In our parent we have a button which will set the flag variable to "true" and our child will be created and will appear on our page.
Ok. Now we have 2 buttons inside our child.
One button is "exit" which is emit an event so the flag variable of parent will set to "false" and the elemint will disappear from our page(It will be destroyed btw). Works as charm. Ok.
The second button "remake". It emit event so the flag variable will be just toggled (off then on). Simple. We set to "false", we set to "true". So the current child should dissapear, and then imediatly will be created new one.
But here we are facing the problem! Ok, current child is still here, there is no any recreation, it just updates current one... So in child I have checked our lifecycle hooks - created and unmounted via console.log function. And the second button dont trigger them. Start->Exit->Start != Start->Remake.
So can anyone please explain me why this is happening? I cant figure it out.
Interesting thing, if you can see there is some asynchronous code commented in my demo. If we set our flag to "true" inside the async function the child will be recreated and we will see the created hook message but it seems like crutch. We also can add a :key to our component and update it to force rerender, but it also seems like a crutch.
Any explanations on this topic how things work would be nice.
Vue re-uses elements and components whenever it can. It will also only rerender once per tick. The length of a 'tick' is not something you should worry yourself about too much, other than that it exists. In your case the this.exit() and this.started = true statements are executed within the same tick. The data stored in this.started is both true in the last tick and the current tick as it does not end the tick in between the statements, and so nothing happens to your component.
In general you should think in states in Vue rather than in lifecycles. Or in other words: What are the different situations this component must be able to handle and how do you switch between those states. Rather than determining what to do in which point in time. Using :key="keyName" is indeed generally a crutch, as is using import { nextTick } from 'vue'; and using that to get some cadence of states to happen, as is using a setTimeout to get some code to execute after the current tick. The nasty part of setTimeout is also that it can execute code on a component that is already destroyed. It can sometimes help with animations though.
In my experience when people try to use lifecycle hooks they would rather have something happen when one of the props change. For example when a prop id on the child component changes you want to load data from the api to populate some fields. To get this to work use an immediate watcher instead:
watch: {
id: {
handler(newId, oldId) {
this.populateFromApi(newId);
},
immediate: true
}
}
Now it will call the watcher on component creation, and call it afterwards when you pass a different id. It will also help you gracefully handle cases where the component is created with a undefined or null value in one of the props you expect. Instead of throwing an error you just render nothing until the prop is valid.

Vue object passed through emit not recognized after listened in the other component

I have two component say componentA and componentB.
In componentA I have a click event that triggers a method that passed the user object as the argument, and this object is then emitted using the event bus. When I successfully listened to the emit in componentB I have assigned the object that was emitted to a variable, I console.log out the variable it showed the object, but when I console.log again the variable outside of the Event.$once the object disappeared.
Any help would be appreciated :)
Thanks!
Here's my code:
componentA
showUserStats(user) {
EventBus.$emit('userInfo', user);
this.$router.push({
name: 'componentB',
params: { id: user._id }
})
}
componentB
created() {
EventBus.$once('userInfo', (user) => {
this.userInfo = user
console.log('userInfo', this.userInfo);
});
console.log('userInfo outside EventBus', this.userInfo);
}
Here is the image of the output
This is happening because you're console logging on created lifecycle event. The console log outside of the $once call is called immediately on component creation (when the user info isn't assigned) but the console.log inside the $once is happening only when the event is emitted and in turn the varible / data is assigned.
In line with comments: You can't just throw a delay in and expect it to work as it's based on events firing. You've not given us info on when showUserStats is called so I don't know when this event emits.
With regards to the computed value, that should work as it'll update when the user info is assigned but you'll need to add a check for when it isn't so, something like this:
computed: {
fullName () {
return this.userInfo ? this.userInfo.name + ' ' + this.userInfo.surname : ''
}
}
It'll show blank unless the user info object has been assigned (note: You might need to alter the check based on how you initialise the userInfo var in data ())

How can I capture click event on custom directive on Vue.js?

I am trying to learn Vue.js and came to an practice example where I need to implement a custom directive whice works lice 'v-on'.
This means that i need to capture the click event on my custom directive and call a method.
The template i was thinking of.
<template>
<h1 v-my-on:click="alertMe">Click</h1>
</template>
The problem is i don't know how to capture the click event in the custom directive. Excuse the clumsy code below.
<script>
export default {
methods: {
alertMe() {
alert('The Alert!');
}
},
directives: {
'my-on': {
bind(el, binding, vnode) {
console.log('bind');
el.addEventListener('click',()=>{
console.log('bind');
vnode.context.$emit('click');
});
},
}
}
}
</script>
Can anyone help me understand how this works? I didn't manage to find any example of something similar.
After some more searching i came to this solution:
<template>
<h1 v-my-on:click="alertMe">Click me!</h1>
</template>
<script>
export default {
methods: {
alertMe() {
alert('The Alert!');
}
},
directives: {
'my-on': {
// Add Event Listener on mounted.
bind(el, binding) {
el.addEventListener(binding.arg, binding.value);
},
// Remove Event Listener on destroy.
unbind(el, binding) {
el.removeEventListener(binding.arg, binding.value);
}
}
}
}
</script>
The solution you found is, as far as I can tell, the very best solution for what you are looking for. However, for those who don't know much about Vue.JS I thought I'd give a quick explanation. I'd also suggest you check out the official Vue documentation for Custom Directives or my Medium article on the concepts.
This is the code that Vlad came to and I would support:
<template>
<h1 v-my-on:click="alertMe">Click me!</h1>
</template>
<script>
export default {
methods: {
alertMe() {
alert('The Alert!');
}
},
directives: {
'my-on': {
bind(el, binding) {
let type = binding.arg;
let myFunction = binding.value;
el.addEventListener(type, myFunction);
}
}
}
}
</script>
In short, Vue Directives are called on the lifecyle of the element they are attached to, based on the directive object definition. In the example the function defined is called "bind" so the directive will call that function when the element is bound into the DOM.
This function receives the element it's attached to "el" and the different content of the directive usage in the template "binding". In the binding usage in the template, the value after the colon ":" is the "arg" which in this example is the string literal "click". The value inside of the quotes '""' is the "value" which in this case is the object reference to the function "alertMe".
The vars that are defined by getting binding.arg and binding.value (with their respective content) can then be used to create an event listener contained inside of the element "el" that the directive is used on (el is modifiable). So, when the element is created and bound, this new event listener is created on the "click" event defined by "arg" and it will call the "alertMe" function defined by "value".
Because the modification is contained inside the element, you don't have to worry about cleaning up on unbind, because the listener will be destroyed when the element is destroyed.
And that is a basic description of what is happening in the suggested code. To see more about directives and how to use them, follow the suggested links. Hopefully that helps!
You need to register a listener for the event being emitted within your directive.
// emit a custom event
// binding.expression is alertMe
vnode.context.$emit(binding.expression);
// listen for the event
export default {
created(){
this.$on('alertMe', event => {
this.alertMe()
})
},
....
}
This is not calling the method alertMe, rather passing alertMe through to the directive as the binding expression:
<h1 v-my-on:click="alertMe">Click</h1>
#Vlad has an excellent solution!
May I also add an important point: if you wanna pass parameters to your callback, it will confuse you by the way Vue handles your expression. In short, for custom directives, whatever in between quotation marks gets evaluated and the resulted value is passed in (hence, you can get it via binding.value (duh!), while for built-in directives, at least for v-on, the contents between quotation marks get evaluated later on, when event is fired.
Maybe this is best demonstrated with a comparison between custom directive and the built-in v-on directive. suppose you have a "my-on" directive written exactly as what #Vlad does, and you use it side by side with v-on:
built-in:
<h1 v-on:click="myAlert('haha')"> Click me!</h1>
It works as expected, when button is clicked, alert window pops up.
customized:
<h1 v-my-on:click="myAlert('haha')">Click me!</h1>
As soon as button is displayed, the alert window pops up, and when you click on it, the event is fired but nothing visible happens. This is because "myAlert('haha')" is evaluated as soon as binding(?), hence the alert window, and its value gets passed to your directive(undefined or whatever), cuz its value is not a function, nothing seems to happen.
now, the workaround is to have whatever in between the quotation marks returns a function upon evaluation, eg v-my-on:click="() => {myAlert('haha')}"
Hope it helps.
References:
https://stackoverflow.com/a/61734142/1356473
https://github.com/vuejs/vue/issues/5588
As #Vlad said it worked for me:
el.addEventListener('click',()=>{
console.log('bind');
vnode.context.$emit('click');
Here's my directive:
Vue.directive('showMenu', {
bind: function (el, binding, vnode) {
el.addEventListener('click', () => {
console.log('bind')
setTimeout(() => {
this.$emit('toggleDrawer')
}, 1000)
})
}
})
Thanks dude!
Seems like addEventListener works only for native events
To catch events fired with Vue inside the directive use $on
newdirective: {
bind(el, key, vnode){
vnode.componentInstance.$on('event-fired-from-component', someFunction)
},
....
}
You can put this code either inside your component or mixin under directives section like this
directives: {...}
And then connect it to the component you want to receive this event from
<some-component
v-newdirective
></some-component>

Component to emit event on mapGetters

So I load my component, I then call the do something like the following:
created() {
this.$store.dispatch('messages/connect');
this.$store.dispatch('messages/fetchAllMessages');
// this.$emit('set-recipient', this.chats[0]);
},
computed: mapGetters('messages', {
chats: 'getMessages'
}),
The commented section within created is the snippet that I would like to run but only on the creation of this.chats and not on any update there after.
If I try to emit the event where it currently is I get an error: Cannot read property '0' of null.
Hopefully you understand what I mean.
Any ideas?
fetchallMessages calls your server to get the messages, right? that asynchonous process won'T be finshed when the meit is run like that.
If you make sure to return a Promise from that action which resolves after you have added chats, you can do this:
this.$store.dispatch('messages/fetchAllMessages')
.then(() => {
this.$emit('set-recipient', this.chats[0]);
})
If you have trouble returning a Promise from that action, share its implementation and we'll fix it.
If i understand correctly, you want to execute this.$emit('set-recipient', this.chats[0]); only after chats was initialized.
You have 2 options:
don't use mapGetters for the chats getter, just define the computed yourself:
computed: {
...mapGetters('messages')
chats(){
const messages = this.$store.getters.getMessages;
if (messages.length){
this.$emit('set-recipient', this.chats[0]);
}
return messages;
}
}
Instead of doing it in the component, you can move the logic to the store and emit the event from there when you modify chats

Vue.js 2 pass data from component to root instance

I have a component that makes an AJAX request. In the callback function I want to pass a value back to the parent or root instance.
So my callback function for example in the component is:
function callbackFunc(vm, response){
vm.$emit('setValue', response.id);
}
and in my root instance I've tried using a method called setValue like this:
export default {
name: 'app',
data () {
return {
value : ''
}
},
methods: {
setValue: function(value){
console.log(value);
}
}
}
This doesn't work. The documentation seems to say you need to have an event inside the template for it all to get hooked up but that's not going to work in this case.
Any ideas?
Cheers!
I'm using vue-router. So there's the root element that has an App
component and then there'sthe component called Hello which has the
ajax call
In the parent component's template you will have a <router-view><\router-view> which is where the vue-router will put your child. To wire everything up, you need to add the directive to the template:
<router-view v-on:setValue="parentMethod" ><\router-view>
When the child calls $emit("setValue") after the ajax call, it will triggers parentMethod() on the parent. It's not clear why you say it won't work to hook it up in the template. Without the template, there's not really a parent/child relationship.