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.
Related
I'm having trouble getting a route param to pass directly into a component. I followed multiple sets of directions in the docs (including using the Composition API as in the following code), but I'm still getting undefined when the CourseModule.vue first renders.
Route Definition
{
path: '/module/:id',
name: 'Course Module',
props: true,
component: () => import('../views/CourseModule.vue'),
},
CourseModule.vue:
<template>
<div class="AppHome">
<CustomerItem />
<CourseModuleItem :coursemodule-id="this.CoursemoduleId"/>
</div>
</template>
<script>
import { useRoute } from 'vue-router';
import CustomerItem from '../components/customers/customer-item.vue';
import CourseModuleItem from '../components/coursemodules/coursemodule-item.vue';
export default {
setup() {
const route = useRoute();
alert(`CourseModule.vue setup: ${route.params.id}`);
return {
CoursemoduleId: route.params.id,
};
},
components: {
CustomerItem,
CourseModuleItem,
},
mounted() {
alert(`CourseModule.vue mounted: ${this.CoursemoduleId}`);
},
};
</script>
coursemodule-item.vue:
<template>
<div id="module">
<div v-if="module.data">
<h2>Course: {{module.data.ModuleName}}</h2>
</div>
<div v-else-if="module.error" class="alert alert-danger">
{{module.error}}
</div>
<Loader v-else-if="module.loading" />
</div>
</template>
<script>
import Loader from '../APILoader.vue';
export default {
props: {
CoursemoduleId: String,
},
components: {
Loader,
},
computed: {
module() {
return this.$store.getters.getModuleById(this.CoursemoduleId);
},
},
mounted() {
alert(`coursemodule-item.vue: ${this.CoursemoduleId}`);
this.$store.dispatch('setModule', this.CoursemoduleId);
},
};
</script>
The output from my alerts are as follows:
CourseModule.vue setup: zzyClJDQ3QAKuQ2R52AC35k3Hc0yIgft
coursemodule-item.vue: undefined
CourseModule.vue mounted: zzyClJDQ3QAKuQ2R52AC35k3Hc0yIgft
As you can see, the path parameter works fine in the top level Vue, but not it's still not getting passed into the component.
your kebab-cased :coursemodule-id props that you're passing to the CourseModuleItem component becomes a camelCased coursemoduleId props
Prop Casing (camelCase vs kebab-case)
try this
// coursemodule-item.vue
...
props: {
coursemoduleId: String,
},
...
mounted() {
alert(`coursemodule-item.vue: ${this.coursemoduleId}`);
this.$store.dispatch('setModule', this.coursemoduleId);
},
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
I got a <payment-child-component> which handles all the subscriptions and payments, i also have
another <check-active-child-component>
I want these two components to communicate. persay in the <payment-component> a user cancel's his subscription i want to fire a method i have in <check-active-component> which called checkActive()
So from payment-component emits to parent-component when the subscription cancel method is triggered and then fires the checkActive() method inside check-active-component
So if my logic is good, the exact question is: how do i fire a method from parent to child component?
To call a method of a child component from its parent, you can use ref. Here is an example:
Child Component:
export default {
name: "ChildComponent",
methods: {
childMethod(){
console.log("hello from child");
}
}
};
Parent Component:
<template>
<div id="app">
<ChildComponent ref="myChild"/>
</div>
</template>
<script>
import ChildComponent from "./components/ChildComponent";
export default {
name: "App",
components: {
ChildComponent
},
mounted(){
this.$refs.myChild.childMethod()
}
};
</script>
This one is what I am using on my app.
Parent.vue
<template>
<a #click="run">Click me to execute method from child</a>
</template>
<script>
export default {
name:"parent",
methods: {
run() {
this.$root.$refs.child.methodtoexecute();
}
}
}
</script>
Child.vue
<script>
export default {
name:"child",
created() {
this.$root.$refs.child = this;
},
methods: {
methodtoexecute() {
alert("hello from child");
}
}
}
</script>
I learned vue's custom directive today, and start wondering if I can write a custom directive that has same function as v-model. but I find the difficulty to do the two way binding in the directive's hooks, any help?
Yes,
you should pass value props to your component and then emit input for changing value
e.g.:
We have input component:
<template>
<input :value="innerValue" #input="change($event.target.value)">
</template>
<script>
export default {
name: "TextField",
props: ["value"],
computed: {
innerValue() {
return this.value;
}
},
methods: {
change(e) {
console.log(e);
this.$emit("input", e);
}
}
};
</script>
and we use it in parent component:
<template>
<div id="app">
<text-field v-model="value"/>
</div>
</template>
<script>
import TextField from "./components/TextField";
export default {
name: "App",
components: {
TextField
},
data: () => ({
value: ""
})
};
</script>
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
}
}