Reputation: 2021
Suppose I had the following pattern in my vue.js application:
Form.vue:
<template>
<form>
<page-1 v-if="step === 1" :firstName="firstName" :lastName="lastName" @submit="step = 2"></page-1>
<page-2 v-if="step === 2" :email="email" @submit="step = 3"></page-2>
<page-3 v-if="step === 3" :comments="comments" @submit="submit()"></page-3>
</form>
</template>
<script>
import Page1 from './Page1.vue';
import Page2 from './Page2.vue';
import Page3 from './Page3.vue';
export default {
components: {
Page1,
Page2,
Page3
},
data() {
return {
firstName: '',
lastName: '',
email: '',
comments: '',
step: 1
}
},
methods: {
submit() {
// submit the form
}
}
}
</script>
<style>
</style>
Page1.vue:
<template>
<label>First Name:</label>
<input type="text" v-model="firstName"></input>
<label>Last Name:</label>
<input type="text" v-model="lastName"></input>
<button @click="$emit('submit')">Next</button>
</template>
<script>
export default {
props: {
firstName: '',
lastName: ''
}
}
</script>
<style>
</style>
Page2.vue:
<template>
<label>Email:</label>
<input type="email" v-model="email"></input>
<button @click="$emit('submit')">Next</button>
</template>
<script>
export default {
props: {
email: ''
}
}
</script>
<style>
</style>
Page3.vue:
<template>
<label>Comments:</label>
<textarea v-model="comments"></textarea>
<button @click="$emit('submit')">Submit</button>
</template>
<script>
export default {
props: {
comments: ''
}
}
</script>
<style>
</style>
Essentially, it's a form with three pages. The parent component is the form and each page is a child component. The parent component keeps track of the form fields with its data and passes them to each page by props. But this is a bad design because I'm mutating the props in each child component by assigning them to v-model on each input.
Most solutions to this problem I've seen on Google suggest using computed properties but because I'm using v-model, I need to maintain two-way binding and computed properties seem one-way only. So I'm wondering if the following is a common pattern for resolving this problem which is considered a good design:
Form.vue:
<template>
<form>
<page-1 v-if="step === 1" :firstName="firstName" :lastName="lastName" @first-name-changed="val => firstName=val" last-name-changed="val => lastName=val" @submit="step = 2"></page-1>
<page-2 v-if="step === 2" :email="email" @email-changed="val => email=val" @submit="step = 3"></page-2>
<page-3 v-if="step === 3" :comments="comments" @comments-changed="val => comments=val" @submit="submit()"></page-3>
</form>
</template>
<script>
import Page1 from './Page1.vue';
import Page2 from './Page2.vue';
import Page3 from './Page3.vue';
export default {
components: {
Page1,
Page2,
Page3
},
data() {
return {
firstName: '',
lastName: '',
email: '',
comments: '',
step: 1
}
},
methods: {
submit() {
// submit the form
}
}
}
</script>
<style>
</style>
Page1.vue:
<template>
<label>First Name:</label>
<input type="text" v-model="firstNameData"></input>
<label>Last Name:</label>
<input type="text" v-model="lastNameData"></input>
<button @click="$emit('submit')">Next</button>
</template>
<script>
export default {
props: {
firstName: '',
lastName: ''
},
data() {
return {
firstNameData: '',
lastNameData: ''
}
},
watch: {
'firstNameData': function() {
this.$emit('first-name-changed', this.firstNameData);
},
'lastNameData': function() {
this.$emit('last-name-changed', this.lastNameData);
}
},
created() {
this.firstNameData = this.firstName;
this.lastNameData = this.lastName;
}
}
</script>
<style>
</style>
Without repeating the same pattern in the other two child components, the idea is to copy the values of the props to data variables in the child components, bind the data variables to the inputs, watch for changes in the data variables and emit events back up to the parent when they change. These emits would come with the new values and the parent would assign these new values to its data variables.
I don't need to worry about the props passed to each child component changing dynamically. The only reason I want to pass them down to the child components is because the user might want to re-load the form from a draft (otherwise, there would be no need for props at all).
My question is: is this a good design pattern?
Upvotes: 0
Views: 340
Reputation: 1419
Here are some ways to handle this scenario, Vuex is a good option, but for three pages, I think is not necessary.
Using the .sync
modifier
<Child :name.sync="childName"></Child>
And in the child component, you can update the value with an event
this.$emit('update:name', newName);
you can listen an event from child an update the parent data property
//parent component
<div>
<input-name @updateName="eventToUpdateName" /> <!--child component-->
</div>
...
data: () => ({ nameFromChild: '' )},
methods: {
eventToUpdateName(value) {
this.nameFromChild = value; // Update from child value emitted
}
}
...
And in the child component
// Child component
<input v-model="name" />
...
data: () => ({ name: '' }),
// watch for changes in the name property and emit an event, and pass the value to the parent
watch: { name() { this.$emit('updateName', this.name } }
...
Also, You can use a v-model directive and emit 'input' event from child.
//parent component
<div>
<input-name v-model="nameFromChild" /> <!--child component-->
</div>
...
data: () => ({ nameFromChild: '' )}
...
Now in the child component you can have
// Child component
<div>
<input v-model="name" />
</div>
data: () => ({ name: '' }),
props: { value: { type: String, default: '' },
created() { this.name = this.value }, // You can receive a default value
watch: { name() { this.$emit('input', this.name } }
...
Upvotes: 0