Vue js stop second event - vue.js

I have input with some info.
On blur event or on enter press I want to do some action
But when I press enter my input loses focus and two events are fired one after another-what do I do?
<input v-on:keyup.13.prevent ='save_renamed_group(g)'
#blur.prevent = 'save_renamed_group(g)'>
UPD: I don't consider my question as duplicate of this one:
Prevent both blur and keyup events to fire after pressing enter in a textbox
simply because I want a clear and clean and nice solution to this simple and common stuff and all solutions posted there look like a little bit of hell.

Solution 1: apply debounce on the method.
Debouncing essentially groups your events together and keeps them from
being fired too often. To use it in a Vue component, just wrap the
function you want to call in lodash’s _.debounce function.
https://alligator.io/vuejs/lodash-throttle-debounce/
import { debounce } from 'lodash';
export default {
methods: {
// group all function calls within 100ms together
// no matter how many times this function is called within 100ms, only 1 of them will be executed.
save_renamed_group: debounce(g => {
// ...
}, 100),
},
};
Pros: simple
Cons: delayed function execution
Solution 2: store state of function execution in a variable
export default {
created() {
// create the variable
this.save_renamed_group_running = false;
},
methods: {
save_renamed_group(g) {
// exit if function is already running
if (this.save_renamed_group_running) return;
// set running to true
this.save_renamed_group_running = true;
// .... other logic ...
// set running to false before exiting
this.save_renamed_group_running = false;
return /* something */;
},
},
};
Pros: immediate function execution
Cons: verbose

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.

better way to handle multiple emits in vue

I have emits to do if I enter a page to do some magic with a menu, I have a solution but its seems very static and too much code for such fancy modern things like vue or quasar.
On every component I need to emit a event I use this for example:
this.$root.$emit('category-one--name')
And to receive the emit event and handle stuff I use this:
this.$root.$on('category-one--name', this.setSelectBox1)
this.$root.$on('category-otherone--name', this.setSelect2)
this.$root.$on('category-more--name', this.setSelectBox3)
this.$root.$on('category-somemore--name', this.setSelect4)
this.$root.$on('category-ansoson--name', this.setSelectBox5)
then I handle stuff with the following:
setSelectBox1() {
this.model = this.categories[1]
},
setSelectBox2() {
this.model = this.categories[2]
},
Is there a better way, for example give the emitted event an Id or something and then to iterate over all in one method and not just to repeat the code?
thanks
Emit function accept a value as second param so try this:
this.$root.$emit('category-change', this.name);
Then:
this.$root.$on('category-change', this.setSelectBox);
setSelectBox(category) {
// set model here
},

You may have an infinite update loop in a component render function error - solution?

I am getting error "You may have an infinite update loop in a component render function." What should I do?
I have tried making the arrays a data value. Also, I have tried using a for loop. It seems like it's isolated in the first method.
data() {
return {
activeTab: 0,
uniqueLobs: []
}
},
methods: {
addDollarSymbol(val){
var newVal = "$" + val;
return newVal.replace(/<(?:.|\n)*?>/gm, ''); // Trims white space
},
removeDuplicateLOB(lineOfBusiness) {
// Removes duplicate LOBs for tabs
let incomingLobs = [];
lineOfBusiness.forEach((business) => {
incomingLobs.push(business.line_of_business.name);
});
this.uniqueLobs = [...new Set(incomingLobs)];
return this.uniqueLobs;
},
showSpecificLobData(activeTab){
//compares tab LOB to all incoming card data and shows only the LOB data for that specific tab
let activeTabData = [];
this.product_rate_card.forEach((product) => {
if (product.line_of_business.name == this.uniqueLobs[activeTab] ) {
activeTabData.push(product);
}
});
return activeTabData;
}
}
The 'loop' in this case refers to an infinite recursion rather than a for loop.
That warning is logged here:
https://github.com/vuejs/vue/blob/ff911c9ffef16c591b25df05cb2322ee737d13e0/src/core/observer/scheduler.js#L104
It may not be immediately obvious what most of that is doing but the key part of the code is the line if (circular[id] > MAX_UPDATE_COUNT) {, which is checking whether a particular watcher has been triggered more than 100 times.
When reactive data changes it will cause any components that depend on that data to be re-rendered. If the rendering process changes that same data then rendering will be triggered again. If the data never stabilizes then this will continue forever.
Here's a simple example of a component that triggers that warning:
<template>
<div>
{{ getNextCount() }}
</div>
</template>
<script>
export default {
data () {
return {
count: 1
}
},
methods: {
getNextCount () {
this.count++
return this.count
}
}
}
</script>
The template has a dependency on count but by calling getNextCount it will also change the value of count. When that value changes the component will be re-added to the rendering queue because a dependency has changed. It can never break out of this cycle because the value keeps changing.
I can't say for sure what is causing this problem in your component as you haven't posted enough code. However, it could be something like the line this.uniqueLobs = ..., assuming that is being called during rendering. In general I would suggest avoiding changing anything on this during the rendering phase. Rendering should be read-only. Generally you'd use computed properties for any derived data that you want to keep around.
Most times it’s as a result of how you're passing props to another component.
If it’s Vue.js 2, try using v-on:[variable-name].

access methods from the vue current component

Can any one guide or suggest how to resolve this below issue.
Use Case: Trying to implement notification component
Scenario: I am trying to call a method or change the state of the data on triggering of event in Vue.
I have defined the event listener on mounted function and trying to access one of the method.
Basically, the alert within event function is getting triggered, where as alert inside method is not getting triggered, and even any data manipulation is not executing even with in event function.
Where am i missing? is it incorrect to alter state within Event listener?
Basically i am trying to implement notification feature which automatically disappear after few seconds
Any help is appreciated.
Thanks,
Girish
There is another reason,this inside callback function is not Vue component. You can assign var self = this and use inside the callback, or use arrow function.
mounted: function () {
var self = this
EventBus.$on('show', function () {
self.test()
self.show = true
})
},
methods: {
test () {
console.log('Inside methods')
}
}
I believe your problem is the spelling error instead of
method: {}
, use methods: {}
Example:
Error.
method: {
test: function () {
alert('Inside Method');
}
correct.
methods: {
test: function () {
alert('inside method);
}
}
I know it does not have much to do with the question, but be careful when using the event bus, it would be as if you had a speaker, and shouted in the middle of a crowd the name of a person.
Example:
eventbus says Hamilton in the midst of a crowd of 10,000 people.
How many Hamiltons can you have in the middle of this crowd? Use something more specific, such as parent-child communication, avoid using the event bus.

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