Passing Functions as props in vue js - vuejs2

I'm trying to pass editTodo as props function from parent app.vue to child components ...
TodoItem.vue component there is a list Of Items and Time returns to main user input of newTodo and dateTime field. Actually, I'm a new learner of Vue js there is a little knowledge of pass props b/w the components communication.
<template>
<div id="app" class="container">
<TodoInput :addTodo="addTodo"
:updateTodo="updateTodo"
/>
<todo-item v-for="(todo, index) in todos"
:key=todo.id
:todo=todo
:index =index
:removeTodo="removeTodo"
:editTodo="editTodo" />
</div>
</template>
<script>
import TodoInput from "./components/TodoInput.vue";
import TodoItem from "./components/TodoItem.vue";
export default {
name: "App",
components: {
TodoInput,
TodoItem,
},
data() {
return {
editing:false,
editItems:{},
todos: [
// {
// id: 1,
// title: "",
// date: new Date(),
// editing: false,
// completed: false,
// },
// {
// id: 1,
// title: "",
// date: new Date(),
// editing: false,
// completed: false,
// },
],
};
},
methods: {
editTodo(index, newTodo, dateTime){
, ' dateTime ', dateTime)
// this.editItems = {
// id,
// title,
// time,
// }
this.todo = newTodo
this.todo = dateTime
this.selectedIndex = index
this.editing = true
},
TodoItem.vue component there is a list Of Items and Time returns to main user input of newTodo and dateTime field.***enter code here
`**
-->
{{todo.title}}
{{todo.time}}
</div>
<div class="remove-item" #click="removeTodo(index)">
×
</div>
<div class="edit-item" #click="eiditTodo(index)"
>
<i class="fas fa-edit" id="edit"></i>
</div>
export default {
name: 'todo-item',
props:{
todo:{
type: Object,
required: true,
},
removeTodo:{
type:Function,
required:true,
},
index:{
type:Number,
required: true,
},
},
data(){
return{
'id': this.todo.id,
'title': this.todo.newTodo,
'time': this.todo.dateTime,
}
methods:
getEdit(){
this.$emit('editTodo', this.selectedIndex)
}
**`

Instead of passing a function as a prop to the child component in order to run it, you should instead emit an event from the child component and execute the function in the parent component.
To emit an event from the child component
#click="$emit('edit-todo')"
And then in the parent component
<div #edit-todo="editTodo">
</div>
Alternatively, you could just define the editTodo function in theTodoItem component and call it directly.

Related

Update child component value on axios response using v-model

Vue 3
I am trying to update the value of the data variable from the Axios response. If I print the value in the parent component it's getting printed and updates on the response but the variable's value is not updating in the child component.
What I am able to figure out is my child component is not receiving the updated values. But I don't know why is this happening.
input-field is a global component.
Vue 3
Parent Component
<template>
<input-field title="First Name" :validation="true" v-model="firstName.value" :validationMessage="firstName.validationMessage"></input-field>
</template>
<script>
export default {
data() {
return {
id: 0,
firstName: {
value: '',
validationMessage: '',
},
}
},
created() {
this.id = this.$route.params.id;
this.$http.get('/users/' + this.id).then(response => {
this.firstName.value = response.data.data.firstName;
}).catch(error => {
console.log(error);
});
},
}
</script>
Child Component
<template>
<div class="form-group">
<label :for="identifier">{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input :id="identifier" :type="type" class="form-control" :class="validationMessageClass" :placeholder="title" v-model="inputValue">
<div class="invalid-feedback" v-if="validationMessage">{{ validationMessage }}</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
},
validation: {
type: Boolean,
required: false,
default: false,
},
type: {
type: String,
required: false,
default: 'text',
},
validationMessage: {
type: String,
required: false,
default: '',
},
modelValue: {
required: false,
default: '',
}
},
emits: [
'update:modelValue'
],
data() {
return {
inputValue: this.modelValue,
}
},
computed: {
identifier() {
return this.title.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '');
},
validationMessageClass() {
if (this.validationMessage) {
return 'is-invalid';
}
return false;
}
},
watch: {
inputValue() {
this.$emit('update:modelValue', this.inputValue);
},
},
}
</script>
The reason your child will never receive an update from your parent is because even if you change the firstName.value your child-component will not re-mount and realize that change.
It's bound to a property that it internally creates (inputValue) and keeps watching that and not the modelValue that's been passed from the parent.
Here's an example using your code and it does exactly what it's supposed to and how you would expect it to work.
It receives a value once (firstName.value), creates another property (inputValue) and emits that value when there's a change.
No matter how many times the parent changes the firstName.value property, the child doesn't care, it's not the property that the input v-model of the child looks at.
You can do this instead
<template>
<div class="form-group">
<label :for="identifier"
>{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input
:id="identifier"
:type="type"
class="form-control"
:class="validationMessageClass"
:placeholder="title"
v-model="localValue" // here we bind localValue as v-model to the input
/>
<div class="invalid-feedback" v-if="validationMessage">
{{ validationMessage }}
</div>
</div>
</template>
<script>
export default {
... // your code
computed: {
localValue: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
};
</script>
We remove the watchers and instead utilize a computed property which will return the modelValue in it's getter (so whenever the parent passes a new value we actually use that and not the localValue) and a setter that emits the update event to the parent.
Here's another codesandbox example illustrating the above solution.

How to create a method with props passed to child component (Vue3)

I am struggling on the last part of my starter project with outputting the results onto its own component.
I have created a method in the parent component to push the results to an array, and I am then passing that array as props to the child component.
If I just display the array in my child component it works fine, however what I am trying to do is then create a method in my child component based on the results passed via the props.
When I try to do this I am not getting anything outputted, is this something which you can do in Vue?
Parent:
<template>
<button #click="decreaseCount">Decrease</button>
<button #click="increaseCount">Increase</button>
<div class="counterDiv">Counter: {{ count }}</div>
<button #click="calculateResults">Submit</button>
<results v-if="resultsVisible" :results="results"></results>
</template>
<script>
import Results from "./components/Results.vue";
export default {
name: "App",
components: {
Results,
},
data() {
return {
count: 0,
results: [],
resultsVisible: false,
};
},
methods: {
increaseCount() {
this.count += 1;
},
decreaseCount() {
this.count -= 1;
},
calculateResults() {
this.results.push(this.count);
this.resultsVisible = true;
},
},
};
</script>
Child:
<template>
<div class="finalResults">Results: {{ this.resultsText }}</div>
</template>
<script>
export default {
props: ["results"],
data() {
return {
resultsText: "",
};
},
methods: {
returnText() {
if (results < 10) {
this.resultsText = "Below 10";
}
},
},
};
</script>
I have created a very basic example of my problem below
You should emit a custom event from child component which has as handler the parent method:
child :
<template>
<div class="finalResults">Results: {{ this.resultsText }}</div>
</template>
<script>
export default {
props: ["results"],
data() {
return {
resultsText: "",
};
},
methods: {
returnText() {
if (this.results < 10) {
this.resultsText = "Below 10";
this.$emit('push-item', this.resultsText )
}
},
},
};
</script>
in parent component :
<results v-if="resultsVisible" :results="results" #push-item="pushResult"></results>
...
methods:{
pushResult(resulttext){...}
....
}

How to get the values from the DOM controls when the models are created dynamically using vuejs

I am creating a dynamic form generator component in which the DOM controls to be displayed are getting populated from an API service. The model key i.e. the :v-model is also getting added dynamically. I am not able to access the data which the user inputs in the DOM control.
The component from where all the data is passed to the form-generator
<template>
<form-generator :schema="schema" :model="model" :options="formOptions"></form-generator>
</template>
<script>
export default {
components: {
FormGenerator
},
data() {
return {
model: {},
schema: {
fields: [{"id":"dcno1","label":"DC No.","model":"dcno","name":"dcno","ref":"dcno","className":"","type":"input","inputType":"text","required":true,"order":2}]
}
}
}
</script>
In the above code, model and schema.fields are getting populated from an async axios API call.
If we pass value in the model for the model named "dcno", the value is getting filled in the input control
Dynamic Form Generator Component
<template>
<v-form v-if='schema!==null' v-model="model.valid" ref="form" lazy-validation>
<v-flex xs6 v-for='(formItem,index) in schema.fields' v-bind:key='index'>
<v-text-field :v-model='formItem.model' ref="formItem.model"
:label='formItem.label' :rules='formItem.rules'
:value='model[formItem.model]'
:type='formItem.inputType' :value='model[formItem.model]'
:id='formItem.id'>
</v-text-field>
</v-flex>
<v-btn class='float-right' color="primary" #click="submitForm">Submit</v-btn>
</v-form>
</template>
<script>
export default {
name: 'form-generator',
components: {
},
props: {
schema: Object,
model: Object
},
methods:{
submitForm: function(e) {
//how to access the form model values here
}
}
}
</script>
When passed a static value to model dcno, :value='model[formItem.model]', the value is getting displayed.
Consider the fields key has a set of controls.
Please help me in getting the values of the form in the submit function.
As you can see in the docs you cannot change the value of a component property, you need to create a support object inside the inner component and emit its value to the main component. Take a look at the example, i create innerModel based on the schema structure then i emit every changes to the innerModel by watching it.
Vue.config.devtools = false;
Vue.config.productionTip = false;
const formGenerator = Vue.component('form-generator', {
props: ['schema', 'value'],
data() {
return {
innerModel: [],
}
},
watch: {
schema: {
deep: true,
immediate: true,
handler() {
this.innerModel = this.schema.fields.map((field) => ({
// pass here any other property you need
name: field.name,
value: field.model,
}));
},
},
innerModel: {
deep: true,
immediate: true,
handler(value) {
this.$emit('input', value);
},
}
},
methods: {
submitForm: function(e) {
e.preventDefault();
// convert innerModel into an object if needed
console.log(this.innerModel);
}
},
template: `
<form #submit="submitForm">
<input
:key="model.name"
v-model="model.value"
v-for="model in innerModel">
<button type="submit">SUBMIT</button>
</form>
`
})
new Vue({
el: '#app',
components: {
formGenerator
},
data: {
model: {},
schema: {
fields: [{
id: "dcno1",
label: "DC No.",
model: "dcno",
name: "dcno",
ref: "dcno",
className: "",
type: "input",
inputType: "text",
required: true,
order: 2
}]
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<form-generator :schema="schema" v-model="model"></form-generator>
<h2>Changing model</h2>
{{ model }}
</div>

Vue.js Component with v-model

I have been able to accomplish a single level deep of v-model two-way binding on a custom component, but need to take it one level deeper.
Current working code:
<template lang="html">
<div class="email-edit">
<input ref="email" :value="value.email" #input="updateInput()"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
</div>
</template>
<script type="text/javascript">
import LineEditor from './LineEditor.vue'
export default {
components: {
LineEditor
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input',{
email: this.$refs.email.value,
body: this.$refs.body.value
})
}
},
data: function(){
return {}
},
props: {
value: {
default: {
email: "",
body: ""
},
type:Object
}
}
}
</script>
Used like this: <email-edit-input v-model="emailModel" />
However, if I add this piece, the value no longer propagates upwards:
<div class="email-edit">
<line-editor ref="email" :title="'Email'" :value="value.email" #input="updateInput()"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
</div>
</template>
<script type="text/javascript">
import LineEditor from './LineEditor.vue'
export default {
components: {
LineEditor
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input',{
email: this.$refs.email.value,
body: this.$refs.body.value
})
}
},
data: function(){
return {}
},
props: {
value: {
default: {
email: "",
body: ""
},
type:Object
}
}
}
</script>
Using this second custom component:
<template lang="html">
<div class="line-edit">
<div class="line-edit__title">{{title}}</div>
<input class="line-edit__input" ref="textInput" type="text" :value="value" #input="updateInput()" />
</div>
</template>
<script type="text/javascript">
export default {
components: {
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input', this.$refs.textInput.value)
}
},
data: function(){
return {}
},
props: {
title:{
default:"",
type:String
},
value: {
default: "",
type: String
}
}
}
</script>
The first code-block works fine with just an input. However, using two custom components does not seem to bubble up through both components, only the LineEditor. How do I get these values to bubble up through all custom components, regardless of nesting?
I've updated your code a bit to handle using v-model on your components so that you can pass values down the tree and also back up the tree. I also added watchers to your components so that if you should update the email object value from outside the email editor component, the updates will be reflected in the component.
console.clear()
const LineEditor = {
template:`
<div class="line-edit">
<div class="line-edit__title">{{title}}</div>
<input class="line-edit__input" type="text" v-model="email" #input="$emit('input',email)" />
</div>
`,
watch:{
value(newValue){
this.email = newValue
}
},
data: function(){
return {
email: this.value
}
},
props: {
title:{
default:"",
type:String
},
value: {
default: "",
type: String
}
}
}
const EmailEditor = {
components: {
LineEditor
},
template:`
<div class="email-edit">
<line-editor :title="'Email'" v-model="email" #input="updateInput"/>
<input :value="value.body" v-model="body" #input="updateInput"/>
</div>
`,
watch:{
value(newValue){console.log(newValue)
this.email = newValue.email
this.body = newValue.body
}
},
methods: {
updateInput: function(value){
this.$emit('input', {
email: this.email,
body: this.body
})
},
},
data: function(){
return {
email: this.value.email,
body: this.value.body
}
},
props: {
value: {
default: {
email: "",
body: ""
},
type: Object
}
}
}
new Vue({
el:"#app",
data:{
email: {}
},
components:{
EmailEditor
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<email-editor v-model="email"></email-editor>
<div>
{{email}}
</div>
<button #click="email={email:'testing#email', body: 'testing body' }">change</button>
</div>
In the example above, entering values in the inputs updates the parent. Additionally I added a button that changes the parent's value to simulate the value changing outside the component and the changes being reflected in the components.
There is no real reason to use refs at all for this code.
In my case, having the passthrough manually done on both components did not work. However, replacing my first custom component with this did:
<line-editor ref="email" :title="'Email'" v-model="value.email"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
Using only v-model in the first component and then allowing the second custom component to emit upwards did the trick.

Vue component data not updating from props

I'm building a SPA with a scroll navigation being populated with menu items based on section components.
In my Home.vue I'm importing the scrollNav and the sections like this:
<template>
<div class="front-page">
<scroll-nav v-if="scrollNavShown" #select="changeSection" :active-section="activeItem" :items="sections"></scroll-nav>
<fp-sections #loaded="buildNav" :active="activeItem"></fp-sections>
</div>
</template>
<script>
import scrollNav from '.././components/scrollNav.vue'
import fpSections from './fpSections.vue'
export default {
data() {
return {
scrollNavShown: true,
activeItem: 'sectionOne',
scrollPosition: 0,
sections: []
}
},
methods: {
buildNav(sections) {
this.sections = sections;
console.log(this.sections)
},
changeSection(e) {
this.activeItem = e
},
},
components: {
scrollNav,
fpSections
}
}
</script>
this.sections is initially empty, since I'm populating this array with data from the individual sections in fpSections.vue:
<template>
<div class="fp-sections">
<keep-alive>
<transition
#enter="enter"
#leave="leave"
:css="false"
>
<component :is="activeSection"></component>
</transition>
</keep-alive>
</div>
</template>
<script>
import sectionOne from './sections/sectionOne.vue'
import sectionTwo from './sections/sectionTwo.vue'
import sectionThree from './sections/sectionThree.vue'
export default {
components: {
sectionOne,
sectionTwo,
sectionThree
},
props: {
active: String
},
data() {
return {
activeSection: this.active,
sections: []
}
},
mounted() {
this.buildNav();
},
methods: {
buildNav() {
let _components = this.$options.components;
for(let prop in _components) {
if(!_components[prop].hasOwnProperty('data')) continue;
this.sections.push({
title: _components[prop].data().title,
name: _components[prop].data().name
})
}
this.$emit('loaded', this.sections)
},
enter(el) {
twm.to(el, .2, {
autoAlpha : 1
})
},
leave(el, done) {
twm.to(el, .2, {
autoAlpha : 0
})
}
}
}
</script>
The buildNav method loops through the individual components' data and pushes it to a scoped this.sections array which are then emitted back to Home.vue
Back in Home.vue this.sections is populated with the data emitted from fpSections.vue and passed back to it as a prop.
When I inspect with Vue devtools the props are passed down correctly but the data does not update.
What am I missing here? The data should react to props when it is updated in the parent right?
:active="activeItem"
this is calld "dynamic prop" not dynamic data. You set in once "onInit".
For reactivity you can do
computed:{
activeSection(){ return this.active;}
}
or
watch: {
active(){
//do something
}
}
You could use the .sync modifier and then you need to emit the update, see my example on how it would work:
Vue.component('button-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
props: ['counter'],
watch: {
counter: function(){
this.$emit('update:counter',this.counter)
}
},
})
new Vue({
el: '#counter-sync-example',
data: {
foo: 0,
bar: 0
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="counter-sync-example">
<p>foo {{ foo }} <button-counter :counter="foo"></button-counter> (no sync)</p>
<p>bar {{ bar }} <button-counter :counter.sync="bar"></button-counter> (.sync)</p>
</div>