I currently run into the issue that my imported function is not referenced during render. It's easier explained by this code:
<template>
<div v-for="(addOn, index) in JSON.parse(this.restaurants[0].categories[categoryId].addons)">
<label>
<span>{{ addOn.name }} (+ ${{ addZeroes(addOn.price) }})</span>
</label>
</div>
</template>
<script>
import { addZeroes } from "../../../js/helpers";
export default {
data() {
return {
// populated via AJAX
restaurants: [
{
categories: []
}
],
}
},
}
</script>
<style>
</style>
and the error is:
[Vue warn]: Property or method "addZeroes" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
What's the proper way to call a helper function inside a Vue template?
Thanks for any hints!
You could add it to your component:
import { addZeroes } from "../../../js/helpers";
export default {
data() {
return {
// populated via AJAX
restaurants: [
{
categories: []
}
],
}
},
methods: {
addZeroes // shorthand for addZeroes: addZeroes
}
}
Related
I'm struggling with how Vue updates props/child components.
Suppose the following component:
<template>
<v-card>
<Modification v-model="newObject"></Modification>
<OtherComponent #close="resetObject"></OtherComponent>
</v-card>
</template>
<script>
import { MyClass } from "classes";
import Modification from "b";
import OtherComponent from "a";
export default {
name: "MyForm",
components: { OtherComponent, Modification },
props: {
existingObject: {
type: [MyClass, typeof undefined],
required: false,
default: undefined
}
},
data() {
return {
newObject: undefined
};
},
created() {
this.newObject =
this.existingObject !== undefined
? this.existingObject.clone()
: new MyClass();
},
methods: {
resetObject() {
this.newObject =
this.existingObject !== undefined
? this.existingObject.clone()
: new MyClass();
}
}
};
</script>
How MyClass is defined:
export class MyClass {
constructor({ a= null, b=null} = {}) {
this.a = a;
this.b = b;
}
toPayload(){
return { a:this.a , b:this.b };
}
clone() {
return new MyClass(this.toPayload());
}
}
This component receives an existing class instance of MyClass, clones it (clone => new MyClass(...)) and passes it to the Modification component which does some modification upon user input. So far so good, the modification works. However once the customEvent is fired and the resetObject method is called the newObject is reset but the Modification component is not updated with the now reset newObject - it still displays the old, modified values. I also checked inside the Modification component wether or not the update happens: It doesn't.
Why is this the case? Am I missing a step? Am I not aware of a Vue specific mechanism?
Note: I found this blog which provides solutions to force the Modificationcomponent to update. For now it seems to hacky for me to be "THE" solution.
Thanks in advance.
EDIT:
Adding a computed property which includes a console.log(JSON.stringify(this.newObject)) fires everytime newObject is updated.
Also adding a <span> {{ newObject.a }} </span> to the template updates evertime.
Both these tests convince me that the variable not only should be but actually IS reactive.
EDIT 2:
The Modification component consists, for now, of 2 Input components.
It looks like this.
<template>
<v-card-text>
<ModifyA v-model="object.a" #input="handleInput" />
<ModifyB v-model="object.b" #input="handleInput" />
</v-card-text>
</template>
<script>
import { MyClass } from "classes";
import ModifyA from "...";
import ModifyB from "...";
export default {
name: "ShiftFormFields",
components: { ModifyA, ModifyB },
props: {
value: {
type: MyClass,
required: true
}
},
data() {
return { object: this.value };
},
methods: {
handleInput() {
this.$emit("input", this.object);
}
}
};
</script>
If I try adding the ModifyA Input into the component instead of the Modification component like this
<template>
<v-card>
<ModifyA v-model="newObject.a"></Modification>
<OtherComponent #close="resetObject"></OtherComponent>
</v-card>
</template>
the resetObject also resets the value shown in the ModifyA component.
You didn't show how MyClass clones your object.
I'm guessing something in there isn't reactive.
You can check by doing console.log() and see what it says on the console.
If it's reactive, it should show something like MyClass {__ob__: Observer}
You can probably use this.$set('propName', value) to fix your problem
Docs: https://v2.vuejs.org/v2/api/#vm-set
Adds a property to a reactive object, ensuring the new property is also reactive, so triggers view updates. This must be used to add new properties to reactive objects, as Vue cannot detect normal property additions (e.g. this.myObject.newProperty = 'hi').
Either there is a typo in your post, or the typo also exists in your code and is the source of your problem.
In your post you're binding "newObjekt" to the Modification component, but your parent component has the property "newObject"
is this the source of your issue?
I found the solution in this answer.
As I edited my original post with the definition of the Modification component
<template>
<v-card-text>
<ModifyA v-model="object.a" #input="handleInput" />
<ModifyB v-model="object.b" #input="handleInput" />
</v-card-text>
</template>
<script>
import ModifyA from "...";
import ModifyB from "...";
export default {
name: "ShiftFormFields",
components: { ModifyA, ModifyB },
props: {
value: {
type: MyClass,
required: true
}
},
data() {
return { object: this.value };
},
methods: {
handleInput() {
this.$emit("input", this.object);
}
}
};
</script>
it shows the "problem" why the Fields ModifyA and ModifyB do not update if the value updates in the parent component.
As seen in the above definition the variable object is only set to the value once the Component is initialized. It follows that object is not reactive on behalf of value.
To solve this one can use the approach of the above mentioned answer:
<template>
<v-card-text>
<ModifyA v-model="object.a" />
<ModifyB v-model="object.b" />
</v-card-text>
</template>
<script>
import { Shift } from "classes";
import ModifyA from "...";
import ModifyB from "...";
export default {
name: "ShiftFormFields",
components: { ModifyA, ModifyB },
props: {
value: {
type: MyClass,
required: true
}
},
data() {
return { object: this.value };
},
watch: {
value(val) {
this.object = val;
},
object(value) {
this.$emit("input", value);
}
}
};
</script>
Due to the watcher, the object variable is updated whenever the value get's updated by the parent.
Do you know how to change a component dynamically with object prop
App.vue
<template>
<div id="app">
<component :is="current['test'].target.name"> </component>
<input type="button" value="click me" #click="change" />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
import Comp from "./components/Comp.vue";
export default {
name: "App",
components: {
HelloWorld,
Comp,
},
data() {
return {
current: {},
};
},
created() {
this.current["test"] = {
index: 0,
target: {
name: "Comp",
},
};
},
methods: {
change() {
const r =
this.current["test"].target.name === "HelloWorld"
? "Comp"
: "HelloWorld";
this.current["test"].target = {
name: r,
};
console.log(this.current["test"]);
},
},
};
</script>
Comp.vue
<template>
<p>Template 2</p>
</template>
HelloWorld.vue
<template>
<p>Template 1</p>
</template>
https://codesandbox.io/s/clever-water-dgbts?file=/src/components/HelloWorld.vue:0-42
The value of the object will change correctly but not the component.
Thank you
The issue here is that the property test is not defined on the object current in the data definition - you're setting the definition in the created() function. This means that Vue does not know to create the reactive getter/setter for that property.
Change your data definition to:
data() {
return {
current: {
test: {
index: 0,
target: {
name: "Comp"
}
}
}
};
}
It is because of the way Vue does its reactivity (requiring pre-defined properties) that I would recommend steering clear of accessing properties as dictionary items i.e. use:
current.test.target.name
instead of
current['test'].target.name
For more information on Vue reactivity see this page: link
few of my components are using same functions, is there a way how don't to copy code to each component but use it globally...?
If you just put the function in the App.vue, then it is still not available in the component...
Write a separate page with the same functionality, and then introduce it where it is needed.
<template>
<div class='demo'>
<module></module>
</div>
</template>
<script>
import module from './components/module'
export default {
components:{module},
data() {
return {
};
},
methods: {
},
created() {
},
mounted() {
},
}
</script>
<style lang='less' scoped>
</style>
EDIT --- SOLVED
It turns out that isn't really a problem, Vue will auto-bind for you so there's no need to bind manually.
END EDIT ---
I'm trying to pass a method to a callback(or event) to a child component.
Everything works great, except that the function executes in the wrong context.
In react, I would bind the functions in the constructor, I'm not sure what's the solution is in Vue.
Example
<template>
<div id="app">
<Header/>
<Tasks
:todos="todos"
#onMarkAsDone="markAsDone"
>
</Tasks>
</div>
</template>
<script>
import Header from './components/Header.vue';
import Tasks from './components/Tasks.vue';
export default {
name: 'my-component',
data() {
return {
name: 'Tasks',
todos: [{
id:0,
text:'Complete this by lunch',
isDone: false
}]
}
},
methods: {
markAsDone(id) {
console.log(this); // refers to the child component
// can't access this.todos
}
},
components: {
Tasks,
Header
}
}
</script>
Here's the solution to it, turns out you can use the 'created' life cycle hook, this is similar to react when binding in a constructor
<template>
<div id="app">
<Header/>
<Tasks
:todos="todos"
#onMarkAsDone="markAsDone"
>
</Tasks>
</div>
</template>
<script>
import Header from './components/Header.vue';
import Tasks from './components/Tasks.vue';
export default {
name: 'my-component',
data() {
return {
name: 'Tasks',
todos: [{
id:0,
text:'Complete this by lunch',
isDone: false
}]
}
},
methods: {
markAsDone(id) {
console.log(this.todos); // can now access the correct 'this'
}
},
created() {
this.markAsDone = this.markAsDone.bind(this);
},
components: {
Tasks,
Header
}
}
</script>
Sub component code
<template>
<ul>
<li
:class="{isDone:todo.isDone}"
:key="todo.id"
v-for="todo in todos">
<input type='checkbox' #change="markAsDone(todo.id)"/>
{{todo.text}}
</li>
</ul>
</template>
<script>
export default {
name: 'Tasks',
props: ['todos'],
methods: {
markAsDone(id) {
this.$emit('onMarkAsDone', id);
}
}
}
</script>
You can return function in a markAsDone method, like this:
markAsDone() {
return id => {
console.log(this.todos);
}
},
and then when passing method as a prop call it:
:onMarkAsDone="markAsDone()"
Then you can call the prop method inside your Child-Component.
That should give you requested functionality without using bind in created() hook.
I try to get the state from the store using the mapState function, But I can't use the generated code that returns the values into my template code ...
<template>
// Some code
<template v-if="!isLoggedIn">
// Some code
</template>
<template v-else>
// Some code
{{ currentUser.name }}
</template>
// Some code
</template>
<script>
import { mapState } from "vuex";
export default {
// Some code
computed: {
...mapState({auth : ['currentUser', 'isLoggedIn','customers']})
}
}
</script>
instead the following code work
<script>
import { mapState } from "vuex";
export default {
// Some code
computed: {
currentUser() {
return this.$store.state.auth.currentUser
},
isLoggedIn() {
return this.$store.state.auth.isLoggedIn
},
}
}
</script>
Warning message
[Vue warn]: Property or method "isLoggedIn" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
Thanks in advance
The right syntax to access non-root properties is the following (using arrow functions) :
computed: {
...mapState({
currentUser: state => state.auth.currentUser,
isLoggedIn: state => state.auth.isLoggedIn,
customers: state => state.auth.customers
})}
Check the documentation.
If you're trying to access values from a namespaced vuex module called auth, pass the name of the module as the first argument and the array of values to map as the second argument:
computed: {
...mapState('auth', ['currentUser', 'isLoggedIn','customers'])
}
You can mapState the module and then use, say this.auth.isLoggedin