unexpected vue warn on a declared prop - vue.js

I'm learning vuex. I'm facing a strange issue after I've migrated some methods to vuex actions.
I get this error in a component that has worked fine until I've migrated some things to vuex and I've implemented inside the component ...mapGetters and ...mapActions
the error is [Vue warn]: Property or method "isVisible" is not defined on the instance but referenced during render
but in my data I've declared the prop
data() {
return {
id: state.userInfo.id,
endCursor: state.userInfo.end_cursor,
nextPageLoaded: false,
isVisible: false,
isVideo: null,
url: null
}
}
<div class="modal fade show" tabindex="-1" role="dialog" v-if="isVisible">
<div class="modal-dialog">
<div class="modal-content h-100 rounded-0">
<div class="modal-header">
<button type="button" class="close mb-3 float-right" #click.prevent="closeZoomModal()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<!-- display image -->
<img class="img-fluid w-100 h-100 img-zoom" :src="url" v-if="!isVideo">
<!-- display video -->
<div class="embed-responsive embed-responsive-4by3 h-100" v-else>
<iframe class="embed-responsive-item h-100" :src="url" title=""></iframe>
</div>
</div>
</div>
</div>
</div>
This happen after the user click on the home component to search for ome data and the result component where the error is fires, is loaded.
How I can fix it? can the error be caused from ...mapGetters or ...mapActions ?

state is not available in data. According to docs, you can pass the instance as first param of the data function
data: vm => ({
isVisible: vm.$store.state.isVisible
})
... but I personally haven't used this (it doesn't work with Typescript and the component is still in an early lifecycle stage and a lot of things are missing from it). Besides, this is merely an assignment (it only runs once - it's not a getter - so if the state changes after data has been set, data won't react to it. You'd have to modify the data prop itself).
So what you need to do is move all store related component properties from data into computed by using either ...mapState() (if they're vuex state props), ...mapGetters() (if they're vuex getters) or use explicit computed syntax:
computed: {
isVisible() {
return this.$store.state.isVisible; // if store state prop
// return this.$store.getters['isVisible'] // if store getter
}
}
If you also want to be able to assign to it (as you would to a data property), you have to replace the above computed syntax (only getter) with a getter + setter syntax:
computed: {
isVisible: {
get() {
return this.$store.state.isVisible;
},
set(value) {
this.$store.dispatch('setVisibility', value);
// you can also commit mutations `this.$store.commit()` from here
}
}
}
If you're still having trouble, please create a minimal reproducible example on codesandbox.io and I'll help sort it out.

Related

Vue: calling method on v-if

I'm working on a Vue project started by someone else and don't have a lot of experience with Vue myself.
With this code:
<div :class="{ 'results': isResults }">
<ais-stats v-slot="{ nbHits }">
<ais-hits
v-if="nbHits"
v-slot="{ items }"
// resultsExist – how is this called?
>
<Product v-for="item in items" :key="item.objectID" :product="item" />
</ais-hits>
<p v-else class="text-center">
No results.
</p>
</ais-stats>
</div>
data() {
return {
isResults: false
};
},
methods: {
resultsExist() {
isResults: true;
}
}
I want to set a class on the parent div when nbHits returns results (from Vue instant-search).
The code is working fine in terms of displaying results or the no results message. But I don't know what the syntax is for calling the resultsExist method.
Assuming that nbHits is an object
You can use dynamic class binding
<div :class="{'className': nbHits }"></div>

How do you pass a prop inside a function inside a template in vue.js?

I am trying to pass a prop inside a function inside a template for a to-do test site I'm making. Basically I want to have a list item which includes the todo item with a button next to it that deletes the same item.
Vue.component("todo-item", {
props: ["todotext"],
template: "<li>{{todotext.text}} <button v-on:click='removeThisItem({{todotext}})'>X</button></li>",
})
var next_id = 3
var app = new Vue ({
el: "#app",
data: {
message: "",
todos: [
{id: 0, text: "Do assignment"},
]
},
methods: {
addTodoItem: function () {
this.todos.push({id: next_id, text: this.message})
next_id += 1
},
removeThisItem: function removeThisItem (item) {
this.todos.splice(this.todos.indexOf(item))
}
}
})
and the HTML
<div id="app">
<input type="text" name="" v-model="message">
<button type="button" name="button" v-on:click="addTodoItem">Add Todo Item</button>
<ul>
<todo-item
v-for="todo in todos"
v-bind:todotext="todo"
v-bind:key="todo.id">
</todo-item>
</ul>
</div>
However I get the error
invalid expression: Unexpected token '{' in removeThisItem({{todotext}})
Is there a way to pass the prop as an argument inside this function inside this template to be able to delete this list item?
Edit: Here is the JSFiddle: https://jsfiddle.net/f6sn52w8/
Thanks!
Well, trying to solve the issue in your jsfiddle I got the error
[Vue warn]: Property or method "outerHTML" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in <TodoItem>)
Anyway, I figure it out what is happening with your code and why it isn't working.
You have the parent component where you are using your todo-item component:
<!-- parent component -->
<div id="app">
<input type="text" name="" v-model="message">
<button type="button" name="button" v-on:click="addTodoItem">
Add Todo Item
</button>
<ul>
<todo-item
v-for="todo in todos"
v-bind:todotext="todo"
v-bind:key="todo.id">
</todo-item>
</ul>
</div>
The method removeThisItem is declared in this component, so it isn't available in the child component <todo-item>, that's why you see the error in the console.
So the way to handle the click to remove the item is by listening for an event in the parent component and emitting the event from the child component:
Note about shorthand: v-bind:todotext="todo" is the same as :todotext="todo", and v-on:click is the same as #click
<!-- parent component -->
<div id="app">
<input type="text" name="" v-model="message">
<button type="button" name="button" v-on:click="addTodoItem">
Add Todo Item
</button>
<ul>
<todo-item
v-for="todo in todos"
:todotext="todo"
:key="todo.id"
#removeItem="removeThisItem"> <!-- listen for the removeItem event and run the removeThisItem method when it's triggered -->
</todo-item>
</ul>
</div>
Now the child component template must be updated:
Vue.component("todo-item", {
props: ["todotext"],
template:
`<li>
{{todotext.text}}
<button #click="$emit('removeItem', todotext)">X</button>
</li>`,
})
The todo-item component will emit the event removeItem when the button is clicked, and will send todotext prop as parameter to the function that will run on the parent (removeThisItem).
An alternative way to explain better this behavior:
Vue.component("todo-item", {
props: ["todotext"],
template:
`<li>
{{todotext.text}}
<button #click="emitEventRemoveItem">X</button>
</li>`,
methods: {
emitEventRemoveItem() {
// this.$emit will emit an event to the parent
// the first parameter is the event name, the second parameter
// is the argument that is expected in the parent method that
// will run when the event is triggered, removeThisItem in this case
this.$emit('removeItem', this.todotext);
}
}
})
Try to run this in your editor, in jsfiddle I got an error. Anyway, the issue is that you're trying to run a method that is declared in the parent component from the child component.
Let me know if it works or if you get any error.

All dynamically generated components are changing to the same value in VUEJS

we are building a chat application in Vuejs, now every chat message is component in our application, now whenever we are changing the value of one chat message, the value of all chat messages changes
What is happening
source code
App Component
const App = new Vue({
el: '#myApp',
data: {
children: [
MyCmp
],
m1: '',
m2: '',
m3: 'Hello world',
m4: 'How are you'
},
methods: {
sendMessage (event) {
if(event.key == "Enter") {
this.m2= this.m3;
this.children.push(MyCmp);
}
},
}
});
component code
let MyCmp = {
props: ['myMessage'],
template: `
<li class="self">
<div class="avatar"><img src="" draggable="false"/></div>
<div class="msg">
<p>{{ myMessage }}</p>
</div>
</li>
`
};
** view where components are generating **
<ol class="chat">
<template v-for="(child, index) in children">
<component :is="child" :key="child.name" v-bind="{myMessage: m3}"></component>
</template>
</ol>
Even though you are creating new components by pushing them into the children array, they are still getting bound to the same data via the line v-bind="{myMessage: m3}". Whenever you change m3, it will be passed down to all the child components and they will all render the same message.
This is an odd way of creating custom components since you could easily do so using the templating syntax or render function provided by Vue.
Solution 1 - Recommended
Change your approach - push message strings instead of card component definitions into children and use MyCmp inside v-for to render the message cards.
So the new template could be refactored as :-
<ol class="chat">
<template v-for="(message, index) in children">
<my-cmp :key="index" :my-message="message"></my-cmp>
</template>
</ol>
And inside App component, you can replace this.children.push(MyCmp) with this.children.push(messageVariable); where messageVariable contains the message that you receive from the input box.
Why is the recommended? This is a standard approach where component lists are rendered based on an array of data. It will be easier to scale and maintain.
Solution 2
You can use the v-once directive to bind the message one-time as static text. The binding won't be updated even if m3 changes on the parent.
Then MyCmp template will become :-
<li class="self">
<div class="avatar"><img src="" draggable="false"/></div>
<div class="msg">
<p v-once>{{ myMessage }}</p>
</div>
</li>
You bind myMessage of all your components instances with one variable m3. So, when m3 is changed myMessage in all instances changes respectively. Use another variable (e.g. msg) for rendering the message and then use myMessage property only for the initialisation of msg, like this:
let MyCmp = {
props: ['myMessage'],
data: function () {
return {
msg: this.myMessage
}
},
template: `
<li class="self">
<div class="avatar"><img src="" draggable="false"/></div>
<div class="msg">
<p>{{ msg }}</p>
</div>
</li>
`
};

this.$emit not passing data to parent element in Vue.js

I'm creating a to-do list in vue.js and the following piece of code in the child element returns undefined
<input v-model="titleText" type='text'>
<input v-model="projectText" type='text'>
<button class='ui basic blue button' v-on:click="sendForm()">
Create
</button>
<button class='ui basic red button' v-on:click="closeForm()">
Cancel
</button>
sendForm () {
if (this.titleText.length > 0 && this.projectText.length > 0) {
this.$emit('create-todo', {
title: this.titleText,
project: this.projectText,
done: false
})
}
this.titleText = ''
this.projectText = ''
this.isCreating = false
}
}
Parent element:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo()"></create-todo>
</div>
</template>
methods: {
createTodo (newTodo) {
console.log(newTodo)
this.todos.push(newTodo)
}
}
The problem is a simple one: when you handle the create-todo event, you're explicitly invoking the createTodo() method without allowing any arguments to be passed to it. You must either allow Vue to interpret which arguments to supply implicitly by omitting the parentheses, or use a combination of spread syntax and the arguments object to explicitly pass all arguments provided by the emitted event into the method call.
Using omission:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo"></create-todo>
</div>
</template>
Using spread syntax + arguments object:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo(...arguments)"></create-todo>
</div>
</template>
Either solution is acceptable. If, however, you need to be able to include additional arguments into your method call in the future, then using the spread syntax + arguments object would be necessary with the additional arguments supplied before the event arguments. For example, if you wanted to supply the values "foo" and "bar" to createTodo() as well, then you would do the following:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo('foo', 'bar', ...arguments)"></create-todo>
</div>
</template>
You haven't shown the v-dom of your parent component, but perhaps you didn't insert your #create-todo listener into the custom component tag. I don't have 50 rep, so I can't comment and ask.

How to fix my simple custom v-on directive that does not execute a method immediately?

I am here to ask some help about why is my custom directive not working properly. I am trying to create my own v-on (named v-myOn) directive that would just change the background color of the text when it is clicked. The problem is that the method is executed instantly when vue js application is started (meaning the element background has the color style already) and not when a certain event happened which is when the element is clicked. Thanks in advance!
Update: Problem as what Phil stated is I execute the function directly in the template. But in this case I am trying to pass an argument to the method changeColor. So how could i prevent it from executing while being able to pass arguments?
Here is my code in App.vue:
<template>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
<h1 v-myOn:click="changeColor('blue')" :style="{background: color}" ref="heading">Directives Exercise</h1>
<!-- Exercise -->
<!-- Build a Custom Directive which works like v-on (Listen for Events) -->
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return{
color: ''
}
},
directives: {
myOn : {
bind(el, binding, vnode) {
el.addEventListener(binding.arg, binding.value)
}
}
},
methods: {
changeColor(color)
{
this.color = color;
}
}
}
</script>
<style>
</style>
You have to wrap in an arrow function otherwise it will get executed immediately as you have discovered and #Phil has pointed out.
<h1 v-myOn:click="() => changeColor('blue')" :style="{background: color}" ref="heading">Directives Exercise</h1>