How to pass form data from parent component to child? - vue.js

I am trying to learn some vuejs and am struggling to understand how to pass data from a parent component to its child component. I know more is required but i'm not sure which way to go. How do pass the name in an input field in the parent component when the submit button is pressed to display in the child component?
I have tried using v-model because from what i have read and understand it is supposed to do what i need but it updates it without me even needing to press the button.
//Parent component
<template>
<div id="app">
<form #submit.prevent="handleSubmit">
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
<input type="submit" value="Submit Name">
</form>
<Name lname="lname" fname="fname"></Name>
</div>
</template>
<script>
import Name from './components/fullName.vue'
export default {
name: 'app',
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
components: {
Name
},
methods: {
handleSubmit() {
submittedFname = fname,
submittedLname = lname
}
}
}
</script>
//child component
<template>
<div id="my-name">
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>
</template>
<script>
export default {
name: 'my-name',
data () {
return {
}
},
props: {
submittedFname: String,
submittedLname: String
}
}
</script>
I am expecting to display the full name on the child component when the button is pressed but instead it is displayed as i am typing it.

//Parent component
<template>
<div id="app">
<form>
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
</form>
<button #click="handleSubmit(fname,lname)">submit</button>
<Name :submittedFname="submittedFname" :submittedLname="submittedLname" ></Name>
</div>
</template>
<script>
import Name from './components/fullName.vue'
export default {
name: 'app',
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
components: {
Name
},
methods: {
handleSubmit(fname,lname) {
this.submittedFname = fname,
this.submittedLname = lname
}
}
}
</script>
//child component
<template>
<div id="my-name">
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>
</template>
<script>
export default {
name: 'my-name',
data () {
return {
}
},
props: {
submittedFname: String,
submittedLname: String
}
}
</script>
in case I forgot some things here are screenshots:
Parent component
child component

v-model means that the fname and lname instance data properties are updated each time the value of their respective input elements changes (it uses the input event behind the scenes). You then pass fname and lname directly as props to the child component. These props are reactive so it behaves as you see and the name is updated as you type.
To only change the name when submit is pressed, you can do this:
Add 2 more data properties in the parent component (e.g. submittedfname and submittedlname)
Add an #submit event listener on the form that copies the values from fname and lname to submittedfname and submittedlname
Use submittedfname and submittedlname as props for the child component.
Working code:
//Parent component
Vue.component('app', {
template: `
<div>
<form #submit.prevent="handleSubmit">
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
<input type="submit" value="Submit Name">
</form>
<name-comp :submittedFname="submittedFname" :submittedLname="submittedLname"></Name>
</div>`,
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
methods: {
handleSubmit() {
this.submittedFname = this.fname;
this.submittedLname = this.lname;
}
}
});
//child component
Vue.component('name-comp', {
template: `
<div>
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>`,
props: {
submittedFname: String,
submittedLname: String
}
});
var vapp = new Vue({
el: '#app',
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<app />
</div>
You were missing ":" in front of your props given to the Name component. Also you didn't use this like in this.lname.

Related

Vuex-ORM two-way-data binding cannot watch a nested object

this question is related to Two way data binding with Vuex-ORM
i tried using a watch with deep to handle a user form like this.
<template>
<div id="app">
<div style="display: inline-grid">
<label for="text-1">Text-1: </label>
<input name="text-1" type="text" v-model="user.name" />
<label for="text-2">Text-2: </label>
<input name="text-2" type="text" v-model="user.lastName" />
<label for="text-3">Text-3: </label>
<input name="text-3" type="text" v-model="user.birth" />
<label for="text-4">Text-4: </label>
<input name="text-4" type="text" v-model="user.hobby" />
</div>
<div>
<h5>Result</h5>
{{ userFromStore }}
</div>
</div>
</template>
<script>
import { mapGetters, mapMutations, mapActions } from "vuex";
export default {
name: "App",
computed: {
...mapGetters({
userFromStore: "getUserFromStore",
messageFromStore: "getMessage",
}),
user: function () {
return this.userFromStore ?? {}; // basically "User.find(this.userId)" inside store getters
},
},
watch: {
user: {
handler(value) {
console.log('called')
// this.updateUser(value);
},
deep: true,
},
},
methods: {
...mapActions({
fetchUser: "fetchUser",
}),
...mapMutations({
updateUser: "updateUser",
}),
},
created() {
this.fetchUser();
},
};
</script>
problem is my watcher is not watching, no matter what i try. as soon as the data came from Vuex-ORM my component is not able to watch on the getters user
Anyone idea why?
User.find(...) returns a model. The properties of that model are not reactive i.e. you cannot perform two-way data binding on items that are not being tracked. Hence your watcher will not trigger.
My advice would be to push your user data as props to a component that can handle the data programmatically.
Or, by way of example, you can simply handle two-way binding manually:
Vue.use(Vuex)
class User extends VuexORM.Model {
static entity = 'users'
static fields() {
return {
id: this.number(null),
name: this.string(''),
lastName: this.string(''),
birth: this.string(''),
hobby: this.string('')
}
}
}
const db = new VuexORM.Database()
db.register(User)
const store = new Vuex.Store({
plugins: [VuexORM.install(db)]
})
User.insert({
data: {
id: 1,
name: 'John',
lastName: 'Doe',
birth: '12/12/2012',
hobby: 'This, that, the other'
}
})
Vue.component('user-input', {
props: {
value: { type: String, required: true }
},
template: `<input type="text" :value="value" #input="$emit('input', $event.target.value)" placeholder="Enter text here...">`
})
new Vue({
el: '#app',
computed: {
user() {
return User.find(1)
}
},
methods: {
update(prop, value) {
this.user.$update({
[prop]: value
})
}
}
})
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex#3.6.2/dist/vuex.min.js"></script>
<script src="https://unpkg.com/#vuex-orm/core#0.36.4/dist/vuex-orm.global.prod.js"></script>
<div id="app">
<div v-if="user" style="display: inline-grid">
<label for="text-1">Name: </label>
<user-input
id="text-1"
:value="user.name"
#input="update('name', $event)"
></user-input>
<label for="text-2">Last name: </label>
<user-input
id="text-2"
:value="user.lastName"
#input="update('lastName', $event)"
></user-input>
<label for="text-3">D.O.B: </label>
<user-input
id="text-3"
:value="user.birth"
#input="update('birth', $event)"
></user-input>
<label for="text-4">Hobby: </label>
<user-input
id="text-4"
:value="user.hobby"
#input="update('hobby', $event)"
></user-input>
</div>
<pre>User in store: {{ user }}</pre>
</div>

How to pass data between components when using Vue-Router

How do I pass data from one component to another when using Vue router?
I'm building a simple CRUD app that have different components.
My conponents are:
App.vue - where I render the router-view
Contacts.vue - where I have the array of objects of contacts
ContactItem.vue - handle how the contact is displayed (gets contact as a prop from contact.vue
AddContact.vue - add new contact
EditContact.vue - edit selected contact
On the AddContact component, I have a form the user fills and then clicks on the submit button to add the form to the main component in Contacts.vue but when I emit an event and call it on the Contacts.vue component, it doesn't work. I get no output but from devtools I can see the event was triggered from AddContact.vue component.
Here is the Github link
<!-- App.vue -->
<template>
<div>
<Navbar />
<div class="container">
<router-view #add-contact="addContact" />
</div>
</div>
</template>
<script>
import Navbar from "./components/layout/Navbar";
export default {
components: {
Navbar
}
};
</script>
<!-- Contacts.vue -->
<template>
<div>
<div v-for="(contact) in contacts" :key="contact.id">
<ContactItem :contact="contact" />
</div>
</div>
</template>
<script>
import ContactItem from "./ContactItem";
export default {
components: {
ContactItem
},
data() {
return {
contacts: [
{
id: 1,
name: "John Doe",
email: "jdoe#gmail.com",
phone: "55-55-55"
},
{
id: 2,
name: "Karen Smith",
email: "karen#gmail.com",
phone: "222-222-222"
},
{
id: 3,
name: "Henry Johnson",
email: "henry#gmail.com",
phone: "099-099-099"
}
]
};
},
methods: {
addContact(newContact) {
console.log(newContact);
this.contacts = [...this.contacts, newContacts];
}
}
};
</script>
<!-- AddContact.vue -->
<template>
<div>
<div class="card mb-3">
<div class="card-header">Add Contact</div>
<div class="card-body">
<form #submit.prevent="addContact">
<TextInputGroup
label="Name"
name="name"
placeholder="Enter your name..."
v-model="name"
for="name"
/>
<TextInputGroup
type="email"
label="Email"
name="email"
placeholder="Enter your email..."
v-model="email"
/>
<TextInputGroup
type="phone"
label="Phone"
name="phone"
placeholder="Enter your phone number..."
v-model="phone"
/>
<input type="submit" value="Add Contact" class="btn btn-block btn-light" />
</form>
</div>
</div>
</div>
</template>
<script>
import TextInputGroup from "../layout/TextInputGroup";
export default {
components: {
TextInputGroup
},
data() {
return {
name: "",
email: "",
phone: ""
};
},
methods: {
addContact() {
const newContact = {
name: this.name,
email: this.email,
phone: this.phone
};
this.$emit("add-contact", newContact);
}
}
};
</script>
Well there are more ways to send data from component to component:
You could create a new Vue instance in your main.js file and call it eventBus
export const eventBus = new Vue();
After that you can import this bus wherever you need it:
import { eventBus } from "main.js"
Then you can send events over this global bus:
eventBus.$emit("add-contact", newContact);
At the other component you need to import this bus again and listen to this event in your "created" lifecycle:
created(){
eventBus.$on("add-contact", function (value){
console.log(value)
})
}
The other way is to store it centralized in vuex state. With "created" you can call this data. Created gets executed after your vue instance is created

How to filter data from a parent component in a child component?

I have a component who show a list of data and I want to filter this list with different filters who are in a child component
I managed to do it with "computed" but only when I put everything in the parent component.
Test.vue
<template>
<div>
<filtres />
<garage v-for="g in garages" v-bind:gar="g" :key="g.id" />
</div>
</template>
<script>
export default {
name: 'test',
components: {
simplecomposant,
garage,
filtres
},
data(){
return{
garages: [],
}
},
mounted(){
var self = this;
axios.get('FETCHAPI').then(function (response)
{
self.garages = response.data.datas;
});
},
}
</script>
Filtres.vue
<template>
<div>
<label><input type="radio" v-model="selectedCity" value="All" /> All</label>
<label><input type="radio" v-model="selectedCity" value="1" /> City 1</label>
<label><input type="radio" v-model="selectedCity" value="2" />City 2</label>
</div>
</template>
<script>
export default {
name : 'filtres',
data(){
return{
selectedCity : "All"
}
},
}
</script>
garage.vue
<template>
<li class="aeris-simple-li">{{gar.name}}</span></li>
</template>
<script>
export default {
name : 'garage',
props: {
gar: {
type: Object
}
},
}
</script>
I want that when I select a filter (all, city 1, ...) it filters the datas "garages" who are in the parent component.

How can I abstract vee-validate errors properties?

I currently have a custom component with v-validate on the parent. I would like to abstract out the "fields.email && fields.email.touched ? errors.first('email') : ''" into it's own method, preferably on a mixin. I have tried creating a method that takes in a name and returns the result of the above, this does not seem to work due to methods not being reactive. I have also tried creating a computed property, but cannot seem to make one that is dynamic. A solution that may work is dynamically creating computed properties, however: i believe this is not possible.
Here is my code:
InputField.vue:
<template>
<label :for='name' :class="{error: error}">
{{ label }}
<input
class='input'
:value="value"
:disabled="disabled"
:name="name"
:type="type"
:placeholder="placeholder"
#input="$emit('input', $event.target.value)"
#change="$emit('change', $event.target.value)"
#blur="$emit('blur')"
/>
<p v-if="error">{{ error }}</p>
</label>
</template>
<script>
export default {
props: {
label: String,
name: String,
type: String,
value: [String, Number],
placeholder: [String, Number],
disabled: {
type: Boolean,
default: false
},
error: String
}
};
</script>
LoginModal.vue
<template>
<div class='login-modal-inner'>
<div class='left-box form-wrapper'>
<h3>Login</h3>
<form #submit.prevent="submitForm('login')">
<input-field
v-model.trim="login.email"
name='email'
label='Email'
type='email'
v-validate="'required|email'"
:error="fields.email && fields.email.touched ? errors.first('email') : ''"
/>
<input-field
v-model="login.password"
name='password'
label='Password'
type='password'
v-validate="'required'"
/>
<app-button class='submit'>Log In</app-button>
</form>
</div>
...
</div>
</template>
<script>
import InputField from "../UI/input/InputField.vue";
import Button from "../UI/input/Button.vue";
import InputValidationError from "../UI/input/Button.vue";
export default {
data() {
return {
register: {
email: "",
password: "",
confirmPassword: ""
},
login: {
email: "",
password: ""
}
};
},
methods: {
submitForm(type) {
this.submitClicked = true;
this.$validator.validate();
this.$emit(`${type}-submit`, this[type]);
},
},
components: {
InputField,
appButton: Button
}
};
</script>

Access infromation of grandparent component from grandchild component in vue.js

I have a parent component in Vue called RecipeView, and it is an inline-component. inside it, i have these components:
comments. and inside comments, i have comment and NewCommentForm, has it shows in the picture below.
I am passing in the RecipeView component the id as a prop, and would like to access it in the NewCommentForm component in order to set an endpoint that i will post to and save the comment.
This is the RecipeView component:
<recipe-view :id="{{$recipe->id}}">
<comments :data="{{$recipe->comment}}"#added="commentsCount++"></comments>
</recipe-view>
and the script for it is this:
<script>
import Comments from '../components/Comments.vue';
export default {
props: ['initialCommentsCount','id'],
components: {Comments},
data(){
return {
commentsCount: this.initialCommentsCount,
recipe_id:this.id
};
}
}
</script>
The comments component looks like this:
<template>
<div>
<div v-for="comment in items">
<comment :data="comment"></comment>
</div>
<new-comment-form :endpoint="'/comments/**Here should go the id from parent RecipeView component**'" #created="add"></new-comment-form>
</div>
</template>
<script>
import Comment from './Comment.vue';
import NewCommentForm from './NewCommentForm.vue';
export default {
props: ['data'],
components: {Comment, NewCommentForm},
data() {
return {
items: this.data,
endpoint: ''
}
},
methods: {
add(comment) {
this.items.push(comment);
this.$emit('added');
}
}
}
</script>
and this is the NewCommentForm component:
<template>
<div>
<div class="field">
<p class="control">
<input class="input"
type = "text"
name="name"
placeholder="What is your name?"
required
v-model="name">
</p>
</div>
<div class="field">
<p class="control">
<textarea class="textarea"
name="body"
placeholder="Have your say here..."
required
v-model="body">
</textarea>
</p>
</div>
<button type="submit"
#click="addComment"
class="button is-medium is-success">send</button>
</div>
</template>
<script>
export default {
props:['endpoint'],
data(){
return {
body:'',
name:'',
}
},
methods:{
addComment(){
axios.post(this.endpoint, {
body:this.body,
name: this.name
}).then(({data}) => {
this.body = '';
this.name = '';
this.$emit('created', data);
});
}
}
}
</script>
Thanks for the help.