How to use Vue Router programatically with native events? - vue.js

I have been trying to use native events with Vue router using this.$router.push and I keep producing the following error:
[Vue warn]: Invalid handler for event "click": got undefined
found in
---> <section>
I can't seem to figure out why and I have to use multiple solutions, all of which produce the above error. Some of them redirect to the page directly before triggering the event.
Things that I have tried:
1.
//in JSX
<section nativeOnClick={this.navigationPage('login')}></section>
// in methods I have the following:
methods: {
navigationPage: function (page) {
this.$router.push(page)
}
}
2.
//in JSX
<section nativeOnClick={this.$router.push('login')}></section>
One solution that works is the following:
//in JSX
<section nativeOnClick={this.navigateToLogin}></section>
// in methods I have the following:
methods: {
navigateToLogin: function () {
this.$router.push(page)
}
}
Could you please let me know what I am doing wrong in 1 and 2. The solution that works is fine but that requires me to create a method for every navigation. I personally like 2!!
Any help is appreciated
NEW
Below is the full component definition:
//navbar.js
import styles from './index.styl'
import logo from 'Src/assets/logo.svg'
import menu from 'Src/assets/menu.svg'
export default {
name: 'NavBar',
methods: {
navigationPage: function (page) {
this.$router.push(page)
}
},
render (h) {
return (
<ui-nav class={styles.nav}>
<ui-section class={styles.navLogo} nativeOnClick={ this.navigationPage('hello') }>
<ui-image source={logo} />
</ui-section>
<ui-spacer />
<ui-section class={styles.navMenu}>
<ui-image source={menu} />
</ui-section>
</ui-nav>
)
}
}
Note: This style also automatically navigates to /hello (i.e. before click event). I do not know why.

Have you tried removing the this reserved word from your jsx native invocation?, for example:
<section nativeOnClick={ navigationPage('login') }></section>
If you can you post the whole script of your single file component (or whatever you are editing) would be helpful for me to be in context.

It looks like there is one solution that works, but I can't understand why it works though!
working solution:
<section nativeOnClick={ () => this.$router.push('hello') }></section>
The thing that is confusing is why passing arguments without the handler produces the error by when wrapping with a function it does not.
i.e
<section nativeOnClick={ this.$router.push('hello') }></section> // GIVES error

Related

Unable to add elements using the setAttribute

I am using the VUE JS code and trying to add the setAttribute to some of the tags.
Here is the code I am using :
changetab() {
const demoClasses = document.querySelectorAll(".delCon__select");
demoClasses.forEach(button => {
button.setAttribute("tabindex", "0");
});
return true;
},
but when I view in the code inspector, It does not show added to it, I have added the above function in computed.
template is like this :
<template>
<el-container class="orders"></el-download>
</template>
You need to make this type of request in Vue's Lifecycles, like: created or mounted.
Something like:
mounted() {
this.changetab()
}
Computed would not be the most appropriate place for this type of action.

Vuetify Centralize Rules [duplicate]

The following code has been written to handle an event after a button click
var MainTable = Vue.extend({
template: "<ul>" +
"<li v-for='(set,index) in settings'>" +
"{{index}}) " +
"{{set.title}}" +
"<button #click='changeSetting(index)'> Info </button>" +
"</li>" +
"</ul>",
data: function() {
return data;
}
});
Vue.component("main-table", MainTable);
data.settingsSelected = {};
var app = new Vue({
el: "#settings",
data: data,
methods: {
changeSetting: function(index) {
data.settingsSelected = data.settings[index];
}
}
});
But the following error occurred:
[Vue warn]: Property or method "changeSetting" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in <MainTable>)
Problem
[Vue warn]: Property or method "changeSetting" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in <MainTable>)
The error is occurring because the changeSetting method is being referenced in the MainTable component here:
"<button #click='changeSetting(index)'> Info </button>" +
However the changeSetting method is not defined in the MainTable component. It is being defined in the root component here:
var app = new Vue({
el: "#settings",
data: data,
methods: {
changeSetting: function(index) {
data.settingsSelected = data.settings[index];
}
}
});
What needs to be remembered is that properties and methods can only be referenced in the scope where they are defined.
Everything in the parent template is compiled in parent scope; everything in the child template is compiled in child scope.
You can read more about component compilation scope in Vue's documentation.
What can I do about it?
So far there has been a lot of talk about defining things in the correct scope so the fix is just to move the changeSetting definition into the MainTable component?
It seems that simple but here's what I recommend.
You'd probably want your MainTable component to be a dumb/presentational component. (Here is something to read if you don't know what it is but a tl;dr is that the component is just responsible for rendering something – no logic). The smart/container element is responsible for the logic – in the example given in your question the root component would be the smart/container component. With this architecture you can use Vue's parent-child communication methods for the components to interact. You pass down the data for MainTable via props and emit user actions from MainTable to its parent via events. It might look something like this:
Vue.component('main-table', {
template: "<ul>" +
"<li v-for='(set, index) in settings'>" +
"{{index}}) " +
"{{set.title}}" +
"<button #click='changeSetting(index)'> Info </button>" +
"</li>" +
"</ul>",
props: ['settings'],
methods: {
changeSetting(value) {
this.$emit('change', value);
},
},
});
var app = new Vue({
el: '#settings',
template: '<main-table :settings="data.settings" #change="changeSetting"></main-table>',
data: data,
methods: {
changeSetting(value) {
// Handle changeSetting
},
},
}),
The above should be enough to give you a good idea of what to do and kickstart resolving your issue.
Should anybody land with the same silly problem I had, make sure your component has the 'data' property spelled correctly. (eg. data, and not date)
<template>
<span>{{name}}</span>
</template>
<script>
export default {
name: "MyComponent",
data() {
return {
name: ""
};
}
</script>
In my case the reason was, I only forgot the closing
</script>
tag.
But that caused the same error message.
If you're experiencing this problem, check to make sure you don't have
methods: {
...
}
or
computed: {
...
}
declared twice
It's probably caused by spelling error
I got a typo at script closing tag
</sscript>
Remember to return the property
Another reason of seeing the Property "search" was accessed during render but is not defined on instance is when you forget to return the variable in the setup(){} function
So remember to add the return statement at the end:
export default {
setup(){
const search = ref('')
//Whatever code
return {search}
}
}
Note: I'm using the Composition API
Adding my bit as well, should anybody struggle like me, notice that methods is a case-sensitive word:
<template>
<span>{{name}}</span>
</template>
<script>
export default {
name: "MyComponent",
Methods: {
name() {return '';}
}
</script>
'Methods' should be 'methods'
If you use two times vue instance. Then it will give you this error. For example in app.js and your own script tag in view file. Just use one time
const app = new Vue({
el: '#app',
});
I got this error when I tried assigning a component property to a state property during instantiation
export default {
props: ['value1'],
data() {
return {
value2: this.value1 // throws the error
}
},
created(){
this.value2 = this.value1 // safe
}
}
My issue was I was placing the methods inside my data object. just format it like this and it'll work nicely.
<script>
module.exports = {
data: () => {
return {
name: ""
}
},
methods: {
myFunc() {
// code
}
}
}
</script>
In my case, I wrote it as "method" instead of "methods". So stupid. Wasted around 1 hour.
Some common cases of this error
Make sure your component has the data property spelled correctly
Make sure your template is bot defined within another component’s template.
Make sure you defined the variable inside data object
Make sure your router name in string
Get some more sollution
It is most likely a spelling error of reserved vuejs variables. I got here because I misspelled computed: and vuejs would not recognize my computed property variables. So if you have an error like this, check your spelling first!
I had two methods: in the <script>, goes to show, that you can spend hours looking for something that was such a simple mistake.
if you have any props or imported variables (from external .js file) make sure to set them properly using created like this;
make sure to init those vars:
import { var1, var2} from './constants'
//or
export default {
data(){
return {
var1: 0,
var2: 0,
var3: 0,
},
},
props: ['var3'],
created(){
this.var1 = var1;
this.var2 = var2;
this.var3 = var3;
}
In my case it was a property that gave me the error, the correct writing and still gave me the error in the console. I searched so much and nothing worked for me, until I gave him Ctrl + F5 and Voilá! error was removed. :'v
Look twice the warning : Property _____ was accessed during render but is not defined on instance.
So you have to define it ... in the data function for example which commonly instantiate variables in a Vuejs app. and, it was my case and that way the problem has been fixed.
That's all folk's !
In my case, I forgot to add the return keyword:
computed: {
image(){
this.productVariants[this.selectedVariant].image;
},
inStock(){
this.productVariants[this.selectedVariant].quantity;
}
}
Change to:
computed: {
image(){
return this.productVariants[this.selectedVariant].image;
},
inStock(){
return this.productVariants[this.selectedVariant].quantity;
}
}
In my case due to router name not in string:
:to="{name: route-name, params: {id:data.id}}"
change to router name in string:
:to="{name: 'router-name', params: {id:data.id}}"
In my case I was trying to pass a hard coded text value to another component with:
ChildComponent(:displayMode="formMode")
when it should be:
ChildComponent(:displayMode="'formMode'")
note the single quotes to indicate text instead of calling a local var inside the component.
If you're using the Vue3 <script setup> style, make sure you've actually specified setup in the opening script tag:
<script setup>
I had lapsed into old habits and only created a block with <script>, but it took a while to notice it.
https://v3.vuejs.org/api/sfc-script-setup.html
Although some answers here maybe great, none helped my case (which is very similar to OP's error message).
This error needed fixing because even though my components rendered with their data (pulled from API), when deployed to firebase hosting, it did not render some of my components (the components that rely on data).
To fix it (and given you followed the suggestions in the accepted answer), in the Parent component (the ones pulling data and passing to child component), I did:
// pulled data in this life cycle hook, saving it to my store
created() {
FetchData.getProfile()
.then(myProfile => {
const mp = myProfile.data;
console.log(mp)
this.$store.dispatch('dispatchMyProfile', mp)
this.propsToPass = mp;
})
.catch(error => {
console.log('There was an error:', error.response)
})
}
// called my store here
computed: {
menu() {
return this.$store.state['myProfile'].profile
}
},
// then in my template, I pass this "menu" method in child component
<LeftPanel :data="menu" />
This cleared that error away. I deployed it again to firebase hosting, and voila!
Hope this bit helps you.
It seems there are many scenarios that can trigger this error. Here's another one which I just resolved.
I had the variable actionRequiredCount declared in the data section, but I failed to capitalize the C in Count when passing the variable as a params to a component.
Here the variable is correct:
data: () => {
return{
actionRequiredCount: ''
}
}
In my template it was incorrect (notd the no caps c in "count"):
<MyCustomModule :actionRequiredCount="actionRequiredcount"/>
Hope this helps someone.
Most people do have an error here because of:
a typo or something that they forgot to declare/use
the opposite, did it in several places
To avoid the typo issues, I recommend always using Vue VSCode Snippets so that you don't write anything by hand by rather use vbase, vdata, vmethod and get those parts generated for you.
Here are the ones for Vue3.
You can of course also create your own snippets by doing the following.
Also make sure that you're properly writing all the correct names as shown here, here is a list:
data
props
computed
methods
watch
emits
expose
As for the second part, I usually recommend either searching the given keyword in your codebase. So like cmd + f + changeSetting in OP's case to see if it's missing a declaration somewhere in data, methods or alike.
Or even better, use an ESlint configuration so that you will be warned in case you have any kind of issues in your codebase.
Here is how to achieve such setup with a Nuxt project + ESlint + Prettier for the most efficient way to prevent bad practices while still getting a fast formatting!
One other common scenario is:
You have a component (child) extending another component (parent)
You have a property or a method xyz defined under methods or computed on the parent component.
Your are trying to use parent's xyz, but your child component defines its own methods or computed
Sample code with the problem
// PARENT COMPONENT
export default {
computed() {
abc() {},
xyz() {} // <= needs to be used in child component
},
...
}
// CHILD COMPONENT
export default {
extends: myParentComponent,
computed() {
childProprty1() {},
childProprty2() {}
}
}
The solution
In this case you will need to redefine your xyz computed property under computed
Solution 1:
Redefine xyz and copy the code from the parent component
// CHILD COMPONENT
export default {
extends: myParentComponent,
computed() {
xyz() {
// do something cool!
},
childProprty1() {},
childProprty2() {}
}
}
Solution 2
Redefine xyz property reusing parent component code (no code redundancy)
// CHILD COMPONENT
export default {
extends: myParentComponent,
computed() {
xyz() {
return this.$parent.$options.computed.xyz
},
childProprty1() {},
childProprty2() {}
}
}
For me it happened because I wrote method: instead of methods: (plural). It's a silly mistake but it can happen :)
In my case it was the methods: { } I had put the } before my method functions so for example I had it like this methods: { function , function }, function, function so some of the functions that were out of the curly braces were not included inside the methods function.

Vue: event-bus handler is undefined

I want to implement event-bus in my Vue app.
First I want to make event-bus calls to start loading.
My main component's script tag where I call the $on look like this:
<script>
import store from '../store.js'
import EventBus from '../event-bus';
import Header from './includes/Header'
import Footer from './includes/Footer'
import Loading from './includes/Loading'
export default {
data() {
return {
isLoading: null
}
},
components: {
Header,
Footer,
Loading
},
mounted() {
EventBus.$on('isLoading'), function (payLoad) {
console.log(payload)
this.isLoading = payload
};
}
}
</script>
But I got this error:
[Vue warn]: Error in event handler for "isLoading": "TypeError: handler is >undefined"
How can I fix it?
You've got two mistakes. It should look like this:
EventBus.$on('isLoading', (payLoad) => {
console.log(payload)
this.isLoading = payload
});
Mistake 1 is the closing ) before the comma, leading to $on being called without a function. This is what is causing the warning message. That closing ) should be at the end, between the } and the ;.
Mistake 2 is using a normal function, leading to this being incorrect inside the function. An arrow function will preserve the this value from the surrounding scope.
I suggest using an IDE or a linter that can pick up on these kinds of problems. Your code was syntactically valid but these kinds of mistakes can easily be detected by suitable tooling.

Add focus to first input on load and next input after success using vue

I am creating a fill in the blank using vue. I have three components, a parent component where I build the question, an input component where I validate and the text component. I have stripped out a lot of the code to try and keep the question relevant, anything commented out is unsuccessful. Hope I am not out in left field on this one but have never attempted this so thought I would try here.
First issue:
Some of the questions have two inputs and I want to auto provide focus to the first input, (using a custom directive ) I am able to gain focus on the last created input. I am not really sure how to access first child I guess. Works well with single input questions though. I have tried doing so by using $refs and $nextTick(()) but no luck.
Second issue:
Once one input gets isCorrect I want to auto focus to the next available non correct element. I figured I would have be able to access the child input from the parent component and have been trying using this link but I have been unsuccessful.
Any help or insights is much appreciated. thanks
What it looks like for visual
What I am after
Parent Component
import ivInput from "../inline-components/iv-input.vue";
import ivText from "../inline-components/iv-text.vue";
<component
:is="buildQuestion(index)"
ref="ivWrap"
v-for="(item, index) in questionParts"
:key="index">
</component>
export default() {
components:{
ivInput,
ivText
},
mounted(){
// console.log(this.$refs.ivWrap)
// console.log(this.$refs.iv)
},
methods: {
buildQuestion: function (index) {
if(this.questionParts[index].includes('*_*')){
return ivInput
}else{
return ivText
}
},
//focus: function (){
// this.$refs.iv.focus()
// console.log(this.$refs.iv)
// }
}
}
Input Component
<div :class="'iv-input-wrap'">
<input
ref="iv"
v-focus
type="text"
v-model="userInput"
:class="[{'is-correct':isCorrect, 'is-error':isError}, 'iv-input']"
:disabled="isCorrect">
</div>
export default{
// directives:{
// directive definition
// inserted: function (el) {
// el.focus()
// },
}
computed{
isCorrect: function () {
if(this.isType == true && this.isMatch == true){
// this.$refs.iv.focus()
// this.$nextTick(() => this.$refs.iv.focus)
return true
}
}
}
}

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>