Vuex Action committing mutation - javascript

I have a vue app where a user can randomize a title and subtitle OR edit the fields using a custom input component.
When a user chooses to edit, I'd like to send the updated title and subtitle from the input component to the store to mutate the title and subtitle state when clicking the save button after filling out the values desired in the input component.
Currently able to pass values from parent to child and had an emit present for the parent to listen to, however, I'm not sure how to update the original values to the custom ones and get "undefined" as a result from the $emit.
I can't seem to find a solution to this problem, all the forums I have been on haven't helped so I really hope someone on here can help me with my problem; would really appreciate it.
Parent.vue
<template>
<main class="home-page page">
<div v-if="!editMode">
<div>
<span>Title: </span>{{title}}
</div>
<div>
<span>Subtitle: </span>{{subtitle}}
</div>
<div>
<button #click="randomizeTitleAndSubtitle">
Randomize
</button>
<button #click="onEdit">Edit</button>
</div>
</div>
<div v-else>
<DoubleInput
:value="{ title, subtitle }"
/>
<div>
<button #click="onCancel">Cancel</button>
<button #click="onSave">Save</button>
</div>
</div>
</main>
</template>
<script>
// # is an alias to /src
import DoubleInput from '#/components/DoubleInput.vue';
import { mapState, mapActions } from 'vuex';
export default {
name: 'Parent',
components: {
DoubleInput,
},
data() {
return {
editMode: false,
};
},
computed: {
...mapState(['title', 'subtitle']),
},
methods: {
...mapActions(['randomizeTitleAndSubtitle', 'updateTitleAndSubtitle']),
onEdit() {
this.editMode = true;
},
onCancel() {
this.editMode = false;
},
onSave() {
this.editMode = false;
const newTitle = this.title;
const newSubtitle = this.subtitle;
this.updateTitleAndSubtitle({ newTitle, newSubtitle });
},
},
mounted() {
this.randomizeTitleAndSubtitle();
},
};
</script>
Child.vue
<template>
<div>
<label>Edit Title: </label>
<input type="text" ref="title" :value="value.title" #input="updateValue()" />
<label>Edit Subtitle: </label>
<input type="text" ref="subtitle" :value="value.subtitle" #input="updateValue()" />
</div>
</template>
<script>
export default {
name: 'Child',
props: ['value'],
methods: {
updateValue() {
this.$emit('input', {
title: this.$refs.title.value,
subtitle: this.$refs.subtitle.value,
});
},
},
};
</script>
Store
import Vue from 'vue';
import Vuex from 'vuex';
import randomWords from 'random-words';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
title: '',
subtitle: '',
},
mutations: {
UPDATE_TITLE(state, value) {
state.title = value;
},
UPDATE_SUBTITLE(state, value) {
state.subtitle = value;
},
},
actions: {
randomizeTitle({ commit }) {
const newTitle = randomWords();
commit('UPDATE_TITLE', newTitle);
},
randomizeSubtitle({ commit }) {
const newSubtitle = randomWords();
commit('UPDATE_SUBTITLE', newSubtitle);
},
randomizeTitleAndSubtitle({ dispatch }) {
dispatch('randomizeTitle');
dispatch('randomizeSubtitle');
},
updateTitleAndSubtitle({ commit }, value) {
const payload = {
title: value.title || null,
subtitle: value.subtitle || null,
};
commit('UPDATE_TITLE', payload);
commit('UPDATE_SUBTITLE', payload]);
},
},
modules: {
},
});

Where I was having the biggest issue was most in the Vuex store, not the parent to child lifecycle like I thought. The emit was working just fine and needed to add in some computed properties to the custom input component. How I was approaching the store was completely backwards and gutted the updateTitleAndSubtitle() action to what's shown below. And lastly, added an #input that would send the updated object of values to onEdit() to set the values to an empty object in the data. Then, use that object with the new values to dispatch/commit to the store! Vualá ~ the desired behavior, no errors, and ended up figuring it out with some time.
What I was missing was passing the new emitted data object to a store action to then mutate the state. The whole concept behind this code challenge was to take in data from the store, modify it through a component, send back the modified data to the store to then change the state. A bit overkill for this, BUT it's the practice and concept I needed to approach a much larger problem in an existing application at work.
Here's the code breakdown!
Custom Input:
<template>
<div>
<label for="title">Edit Title: </label>
<input
type="text"
id="title"
:setTitle="setTitle"
ref="title"
:value="value.title"
#input="updateValue()"
/>
<label for="title">Edit Subtitle: </label>
<input
type="text"
id="subtitle"
:setSubtitle="setSubtitle"
ref="subtitle"
:value="value.subtitle"
#input="updateValue()"
/>
</div>
</template>
<script>
export default {
name: 'DoubleInput',
props: {
value: {
type: Object,
required: true,
},
},
computed: {
setTitle() {
// console.log('set title: ', this.value.title);
return this.value.title;
},
setSubtitle() {
// console.log('set subtitle: ', this.value.subtitle);
return this.value.subtitle;
},
},
methods: {
updateValue() {
this.$emit('input', {
title: this.$refs.title.value,
subtitle: this.$refs.subtitle.value,
});
},
},
};
</script>
Parent:
<template>
<main class="home-page page">
<!-- <span class="bold">Title:</span> {{ title }} <br>
<span class="bold">Subtitle:</span> {{ subtitle }}
<hr> -->
<div v-if="!editMode" class="display-information">
<div class="title">
<span class="bold">Title: </span>{{title}}
</div>
<div class="subtitle">
<span class="bold">Subtitle: </span>{{subtitle}}
</div>
<div class="controls">
<button id="randomize-button" class="control-button" #click="randomizeTitleAndSubtitle">
Randomize
</button>
<button id="edit-button" class="control-button" #click="onEdit">Edit</button>
</div>
</div>
<div v-else class="edit-controls">
<CustomInput
:value="{ title, subtitle }"
#input="v => onEdit(v)"
/>
<div class="controls">
<button id="cancel-button" class="control-button" #click="onCancel">Cancel</button>
<button id="save-button" class="control-button" #click="onSave(v)">Save</button>
</div>
</div>
</main>
</template>
<script>
// # is an alias to /src
import CustomInput from '#/components/CustomInput.vue';
import { mapState, mapActions } from 'vuex';
export default {
name: 'Home',
components: {
CustomInput,
},
data() {
return {
editMode: false,
v: {},
};
},
computed: {
...mapState(['title', 'subtitle']),
},
methods: {
...mapActions(['randomizeTitleAndSubtitle', 'updateTitleAndSubtitle']),
onEdit(v) {
this.editMode = true;
this.v = v;
// console.log('returned value object: ', v);
},
onCancel() {
this.editMode = false;
},
onSave() {
this.editMode = false;
this.$store.dispatch('updateTitleAndSubtitle', this.v);
},
},
mounted() {
this.randomizeTitleAndSubtitle();
},
};
</script>
<style lang="stylus" scoped>
.bold
font-weight bold
.controls
width 100%
display flex
justify-content space-around
max-width 20rem
margin-top 2rem
margin-left auto
margin-right auto
.control-button
height 2.5rem
border-radius 1.25rem
background-color white
border 0.125rem solid black
padding-left 1.25rem
padding-right 1.25rem
&:hover
cursor pointer
background-color rgba(0, 0, 0, 0.1)
</style>
Store:
import Vue from 'vue';
import Vuex from 'vuex';
import randomWords from 'random-words';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
title: '',
subtitle: '',
},
mutations: {
UPDATE_TITLE(state, value) {
state.title = value;
},
UPDATE_SUBTITLE(state, value) {
state.subtitle = value;
},
},
actions: {
randomizeTitle({ commit }) {
const newTitle = randomWords();
commit('UPDATE_TITLE', newTitle);
},
randomizeSubtitle({ commit }) {
const newSubtitle = randomWords();
commit('UPDATE_SUBTITLE', newSubtitle);
},
setTitle({ commit }, value) {
commit('UPDATE_TITLE', value);
},
setSubtitle({ commit }, value) {
commit('UPDATE_SUBTITLE', value);
},
randomizeTitleAndSubtitle({ dispatch }) {
dispatch('randomizeTitle');
dispatch('randomizeSubtitle');
},
updateTitleAndSubtitle({ dispatch }, value) {
dispatch('setTitle', value.title);
dispatch('setSubtitle', value.subtitle);
},
},
modules: {
},
});

Your call to updateTitleAndSubtitle in the onSave() method isn't passing the new title and subtitle to the action.
onSave() {
this.editMode = false;
this.updateTitleAndSubtitle({ title: this.title, subtitle: this.subtitle });
},
Also, I would hesitate using state.title and state.subtitle as your edit mode vars as conventionally you should only change those values via mutations. Instead, pass a local var as the prop to your child component and call the method using it instead.
<DoubleInput v-model="title_subtitle" />
<script>
// ...
data() {
return {
// ...
title_subtitle: {},
};
},
// ...
methods: {
onEdit() {
this.editMode = true;
this.title_subtitle = {
title: this.title,
subtitle: this.subtitle,
};
},
// ...
onSave() {
this.editMode = false;
this.updateTitleAndSubtitle(this.title_subtitle);
},
},
// ...
}
Given your comments it makes more sense why the code is unnecessarily complex. It's perfectly fine to pass your state vars as props as long as your #input handler doesn't try to write back to it. You don't need the local var. Try this:
<CustomInput :value="{ title: this.title, subtitle: this.subtitle }" #input="onSave" />
with
onSave(value) {
this.editMode = false;
this.updateTitleAndSubtitle(value);
}

Related

Vuex - updating data in store from child $emit

I have a Vue app that can either randomize a title and subtitle OR manually edit these two values through a custom input component. When a user decides to edit, their input should then display on save those results on the parent component.
I have the randomizer and child component emitting the updated headings working, but having a troubled time updating the parents and state to display the custom input title and subtitle on save and getting a "undefined" error for both title and subtitle when I placed console logs in updateTitleAndSubtitle() in the actions section of the store.
The objective of this code challenging is to return the new values to the store and be able to display the custom inputs while having the randomizer handy whenever a user decides to use that instead.
Any direction on what I'm doing wrong or missing would be much appreciated. I've been reading article after article around Vuex and Vue2 for 3 days now with 2 months of experience using Vue.
Custom Input Child Component:
<template>
<div>
<label for="title">Edit Title: </label>
<input
type="text"
id="title"
:updateTitle="updateTitle"
v-model="inputTitle"
/>
<label for="title">Edit Subtitle: </label>
<input
type="text"
id="subtitle" :updateSubtitle="updateSubtitle"
v-model="inputSubtitle"
/>
</div>
</template>
<script>
export default {
name: 'CustomInput',
props: {
title: String,
subtitle: String,
},
computed: {
updateTitle() {
console.log('updateTitle: ', this.title);
return this.title;
},
updateSubtitle() {
console.log('updateSubtitle: ', this.subtitle);
return this.subtitle;
},
inputTitle: {
get() {
console.log('set title: ', this.title);
return this.title;
},
set(title) {
console.log('set title: ', title);
this.$emit('input', title);
},
},
inputSubtitle: {
get() {
return this.subtitle;
},
set(subtitle) {
console.log('set subtitle: ', subtitle);
this.$emit('input', subtitle);
},
},
},
};
</script>
Parent component:
<template>
<main class="home-page page">
<div v-if="!editMode" class="display-information">
<div class="title">
<span class="bold">Title: </span>{{title}}
</div>
<div class="subtitle">
<span class="bold">Subtitle: </span>{{subtitle}}
</div>
<div class="controls">
<button id="randomize-button" class="control-button" #click="randomizeTitleAndSubtitle">
Randomize
</button>
<button id="edit-button" class="control-button" #click="onEdit">Edit</button>
</div>
</div>
<div v-else class="edit-controls">
<CustomInput
:title="title"
:subtitle="subtitle"
#update="v => onSave(v)"
/>
<div class="controls">
<button id="cancel-button" class="control-button" #click="onCancel">Cancel</button>
<button id="save-button" class="control-button" #click="onSave">Save</button>
</div>
</div>
</main>
</template>
<script>
// # is an alias to /src
import CustomInput from '#/components/CustomInput.vue';
import { mapState, mapActions } from 'vuex';
export default {
name: 'Home',
components: {
CustomInput,
},
data() {
return {
editMode: false,
};
},
computed: {
...mapState(['title', 'subtitle']),
},
methods: {
...mapActions(['randomizeHeadings', 'updateHeadings']),
onEdit() {
this.editMode = true;
},
onCancel() {
this.editMode = false;
},
onSave(v) {
this.editMode = false;
this.title = v.title;
this.subtitle = v.subtitle;
this.updateTitleAndSubtitle(v);
},
},
mounted() {
this.randomizeHeadings();
},
};
Vuex Store:
import randomWords from 'random-words';
export default new Vuex.Store({
state: {
title: '',
subtitle: '',
},
mutations: {
UPDATE_TITLE(state, value) {
state.title = value;
},
UPDATE_SUBTITLE(state, value) {
state.subtitle = value;
},
},
actions: {
randomizeTitle({ commit }) {
const newTitle = randomWords();
commit('UPDATE_TITLE', newTitle);
},
randomizeSubtitle({ commit }) {
const newSubtitle = randomWords();
commit('UPDATE_SUBTITLE', newSubtitle);
},
randomizeTitleAndSubtitle({ dispatch }) {
dispatch('randomizeTitle');
dispatch('randomizeSubtitle');
},
updateTitleAndSubtitle({ commit }) {
const payload = {
title: this.title || null,
subtitle: this.subtitle || null,
};
commit('UPDATE_TITLE', payload);
commit('UPDATE_SUBTITLE', payload);
},
},
modules: {
},
});
I tested your code in my local development environment and find out that you need a lot of changes in your codes to work better. Here is the new vuex store code:
vuex store:
export default new Vuex.Store({
state: {
title: '',
subtitle: '',
},
mutations: {
UPDATE_TITLE(state, value) {
state.title = value;
},
UPDATE_SUBTITLE(state, value) {
state.subtitle = value;
},
},
actions: {
randomizeTitle({ commit }) {
const newTitle = randomWords();
commit('UPDATE_TITLE', newTitle);
},
randomizeSubtitle({ commit }) {
const newSubtitle = randomWords();
commit('UPDATE_SUBTITLE', newSubtitle);
},
randomizeTitleAndSubtitle({ dispatch }) {
dispatch('randomizeTitle');
dispatch('randomizeSubtitle');
},
updateTitleAndSubtitle({ commit }, inputUser) {
/* I changed the structure of this action to work correctly */
console.log(inputUser);
commit('UPDATE_TITLE', inputUser.title);
commit('UPDATE_SUBTITLE', inputUser.subtitle);
},
},
modules: {
},
});
Also here is the new Parent component code:
Parent component:
<template>
<main class="home-page page">
<div v-if="!editMode" class="display-information">
<div class="title">
<span class="bold">Title: </span>{{title}}
</div>
<div class="subtitle">
<span class="bold">Subtitle: </span>{{subtitle}}
</div>
<div class="controls">
<button id="randomize-button" class="control-button" #click="randomizeTitleAndSubtitle">
Randomize
</button>
<button id="edit-button" class="control-button" #click="onEdit">Edit</button>
</div>
</div>
<div v-else class="edit-controls">
<CustomInput
:title="title"
:subtitle="subtitle"
#titleEvent = "myFuncTitle"
#subTitleEvent = "myFuncSubTitle"
/>
<!--
I removed this part from your component.
#update="v => onSave(v)"
and also added custom events (titleEvent and subTitleEvent) to the component
-->
<div class="controls">
<button id="cancel-button" class="control-button" #click="onCancel">Cancel</button>
<button id="save-button" class="control-button" #click="onSave">Save</button>
</div>
</div>
</main>
</template>
<script>
// # is an alias to /src
import CustomInput from '../components/CustomInput.vue';
import { mapActions } from 'vuex';
export default {
name: 'Parent',
components: {
CustomInput,
},
data() {
return {
editMode: false,
/* defining new data for handling "cancel" button functionality */
temporaryTitle: "",
temporarySubTitle: ""
};
},
computed: {
/* defining setter and getter for each computed value separately */
title: {
// getter
get: function () {
return this.$store.state.title;
},
// setter
set: function (newValue) {
this.$store.commit('UPDATE_TITLE', newValue);
}
},
subtitle: {
// getter
get: function () {
return this.$store.state.subtitle;
},
// setter
set: function (newValue) {
this.$store.commit('UPDATE_SUBTITLE', newValue);
}
},
},
methods: {
/* changing the name of actions according to the names defined in "store" */
...mapActions(['randomizeTitleAndSubtitle', 'updateTitleAndSubtitle']),
onEdit() {
this.editMode = true;
this.temporaryTitle = this.$store.state.title;
this.temporarySubTitle = this.$store.state.subtitle;
},
onCancel() {
this.editMode = false;
this.$store.commit('UPDATE_TITLE', this.temporaryTitle);
this.$store.commit('UPDATE_SUBTITLE', this.temporarySubTitle);
},
myFuncTitle(event) {
console.log(event);
/* we could not set values to "computed" properties, if we had not defined "set: function ..." for them above. */
this.title = event;
},
myFuncSubTitle(event) {
this.subtitle = event;
},
onSave(v) {
this.editMode = false;
console.log(v); /* "v" is not related to your data. notice the console */
// this.title = v.title;
// this.subtitle = v.subtitle;
const payload = {
title: this.title,
subtitle: this.subtitle,
};
this.updateTitleAndSubtitle(payload);
},
},
created() {
this.randomizeTitleAndSubtitle();
},
};
</script>
And finally here is the code of new Custom Input component:
Custom Input:
<template>
<div>
<label for="title">Edit Title: </label>
<input
type="text"
id="title"
v-model="inputTitle"
#input="$emit('titleEvent', $event.target.value)"
/>
<!-- emitting event like above code for each input -->
<label for="title">Edit Subtitle: </label>
<input
type="text"
id="subtitle"
v-model="inputSubtitle"
#input="$emit('subTitleEvent', $event.target.value)"
/>
</div>
</template>
<script>
export default {
name: 'CustomInput',
props: {
title: String,
subtitle: String,
},
computed: {
inputTitle: {
get() {
console.log('set title: ', this.title);
return this.title;
},
set(title) {
console.log('set title: ', title);
},
},
inputSubtitle: {
get() {
return this.subtitle;
},
set(subtitle) {
console.log('set subtitle: ', subtitle);
},
},
},
};
</script>
<style scoped>
</style>
I tried to comment some changes to the codes, but the main changes are related to changing the name of mapActions actions according to the names defined in "store" and also provide a setter for computed properties.
I suggest that you read more in vue and vuex documentations, especially the page that is related to custom events and computed setters and vuex actions, if you have problems with my codes.

Nuxtjs: Axios Request does not work when switching to another route

i try to build a little clothing web shop with nuxtjs. You can choose the color on the details page. The details page represents a pice of clothing. The ColorMenu is a component. If you choose something a color, it will emit it back to the details page and will send a new details request to my backend.
However, changing the color only works if you don't choose another piece of clothing. If you choose another piece of clothing (so the route parameters will change) and choose another color in the menu, there is a always an error that it cannot load anything. it seems that it sends repeated requests until the request is blocked.
The details routes are built according to this scheme: localhost/details/{sellableId}/{ideaId}/{appearanceId}
Details Page:
<template>
<section class="section">
<div v-if="details">
<div class="columns">
<div class="column">
<ImageCaroussel :images="details.images"></ImageCaroussel>
</div>
<div class="column">
<h3>Farben</h3>
<ColorMenu
:appearances="productType.appearances"
:appearanceIds="details.appearanceIds"
></ColorMenu>
</div>
</div>
</div>
</section>
</template>
<script>
import { mapState } from 'vuex'
import Dropdown from '~/components/details/Dropdown.vue'
import ColorMenu from '~/components/details/ColorMenu.vue'
import ImageCaroussel from '~/components/details/ImageCaroussel.vue'
export default {
created() {
this.$nuxt.$on('selected', ($event) => (this.selected = $event))
this.$nuxt.$on('selectedColor', ($event) => this.setSelectedColor($event))
},
data() {
return {
modal: false,
selected: '',
selectedColor: '',
}
},
async asyncData({ store, params }) {
console.log('asyncfirst')
if (params.sellableId && params.appearanceId && params.ideaId) {
await store.dispatch('details/get_details', {
sellableId: params.sellableId,
appearanceId: params.appearanceId,
ideaId: params.ideaId,
})
let sellableId = params.sellableId
let appearanceId = params.appearanceId
let ideaId = params.ideaId
console.log('asyncsecond!')
return { sellableId, appearanceId, ideaId }
}
},
mounted() {
this.sellableId = this.$route.params.sellableId
this.appearanceId = this.$route.params.appearanceId
this.ideaId = this.$route.params.ideaId
console.log('Mounted!')
},
components: {
Dropdown,
ColorMenu,
ImageCaroussel,
},
computed: {
...mapState({
details: (state) => {
return state.details.details
},
currency: (state) => {
return state.sellable.currency
},
productType: (state) => {
return state.details.productType
},
}),
},
methods: {
checkout: async function (sellableId, size, appearanceId) {
let link = await this.$backendrepositories.basket.checkout(
sellableId,
size,
appearanceId
)
if (link.status === 200 && link.data) {
this.modal = true
setTimeout(() => {
window.location.href = link.data.link
}, 3000)
}
},
setSelectedColor: async function (event) {
this.selectedColor = event
await this.$store.dispatch('details/get_details', {
sellableId: this.sellableId,
appearanceId: this.selectedColor,
ideaId: this.ideaId,
})
},
},
}
</script>
ColorMenu Component:
<template>
<div>
<div
v-for="(cell, index) in appearances"
:key="index"
style="display: inline-block"
>
<label v-if="appearanceIds.includes(cell.id)" class="self-container">
<input type="radio" checked="checked" name="color" />
<span
class="checkmark"
:style="`background-color: ${cell.colors[0].value}`"
#click="select(cell.id)"
></span>
</label>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
selected: '',
}
},
props: ['appearances', 'appearanceIds'],
methods: {
select(select) {
this.selected = select
this.$nuxt.$emit('selectedColor', this.selected)
},
},
}
</script>
There is a live demo at https://akano-frontend.vercel.app/

Vue 3: How to update component props value correctly using composition api?

I have like this component:
<template>
<div>
<p>Current coords: <strong>{{ coords }}</strong></p>
<button type="button" #click="updateCoords">
</div>
</template>
<script>
export default {
props: {
coords: {
type: Array,
required: true
}
},
setup(props) {
const updateCoords = () => {
props.coords = [38.561785, -121.449756]
// props.coords.value = [38.561785, -121.449756]
}
return { updateCoords }
},
}
</script>
I tried update prop coords value with updateCoords method but I get error message:
Uncaught TypeError: Cannot set properties of undefined (setting
'coords')
How I can correctly update props value in my case?
Props are readonly:
https://v3.vuejs.org/guide/component-props.html#one-way-data-flow
If you want to have two way binding for props, you'll need to implement the v-model pattern:
https://v3-migration.vuejs.org/breaking-changes/v-model.html#_3-x-syntax
<template>
<div>
<p>Current coords: <strong>{{ coords }}</strong></p>
<button type="button" #click="updateCoords">
</div>
</template>
<script>
export default {
props: {
modelValue: {
type: Array,
required: true
}
},
emits: ['update:modelValue'],
setup(props, {emit}) {
const updateCoords = () => {
emit('update:modelValue', [38.561785, -121.449756])
}
return { updateCoords }
},
}
</script>

How do I render a child component within an iframe in Vue?

So I want to show the user a preview of what an email will look like before it's sent out. To avoid styles from leaking from the parent page into the preview, I've decided to use an iframe. I want the preview to update in real time as the user enters form details.
How would I render a component within an iframe so that its props update automatically when the parent form is updated? This is the code I have so far:
this is the html:
<template>
<div id="confirmation">
<h2>Give a gift</h2>
<form #submit.prevent="checkout()">
<div class="date-section">
<label class="wide">Send</label>
<input type="radio" name="sendLater" v-model="sendLater" required :value="false">
<span>Now</span>
<input type="radio" name="sendLater" v-model="sendLater" required :value="true">
<span style="margin-right: 5px;">Later: </span>
<date-picker :disabled="!sendLater" v-model="date" lang="en" />
</div>
<div>
<label>Recipient Email</label>
<input type="email" class="custom-text" v-model="form.email" required>
</div>
<div>
<label>Recipient Name</label>
<input type="text" class="custom-text" v-model="form.name" required>
</div>
<div>
<label>Add a personal message</label>
<textarea v-model="form.message" />
</div>
<p class="error" v-if="error">Please enter a valid date.</p>
<div class="button-row">
<button class="trumpet-button" type="submit">Next</button>
<button class="trumpet-button gray ml10" type="button" #click="cancel()">Cancel</button>
</div>
</form>
<iframe id="preview-frame">
<preview-component :form="form" :sender-email="senderEmail" :term="term" />
</iframe>
</div>
</template>
here is the js (note: PreviewComponent is the actual preview that will be rendered in the iframe):
export default {
name: 'ConfirmationComponent',
components: {
DatePicker,
PreviewComponent
},
props: {
term: {
required: true,
type: Object
}
},
data() {
return {
form: {
name: null,
email: null,
message: null,
date: null
},
date: null,
sendLater: false,
error: false
}
},
computed: {
senderEmail() {
// utils comes from a separate file called utils.js
return utils.user.email || ''
}
},
watch: {
'form.name'(val) {
this.renderIframe()
},
'form.email'(val) {
this.renderIframe()
}
},
methods: {
renderIframe() {
if (this.form.name != null && this.form.email != null) {
console.log('rendering iframe')
// not sure what to do here......
}
}
}
}
I've tried all sorts of things but what seems to be the hardest is setting the props of the preview-component properly. Any help you all can give would be appreciated.
So as posted in one of the comments, Vuex works perfectly for this.
I also ended up creating a custom "IFrame" component that renders whatever you have inside its slot in an iframe.
Here is my Vuex store:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
form: {
name: null,
email: null,
message: null
},
senderEmail: null,
term: null,
styles: null
},
mutations: {
updateForm(state, form) {
state.form = form
},
updateEmail(state, email) {
state.senderEmail = email
},
updateTerm(state, term) {
state.term = term
},
stylesChange(state, styles) {
state.styles = styles
}
}
})
my IFrame component:
import Vue from 'vue'
import { store } from '../../store'
export default {
name: 'IFrame',
data() {
return {
iApp: null,
}
},
computed: {
styles() {
return this.$store.state.styles
}
},
render(h) {
return h('iframe', {
on: {
load: this.renderChildren
}
})
},
watch: {
styles(val) {
const head = this.$el.contentDocument.head
$(head).html(val)
}
},
beforeUpdate() {
this.iApp.children = Object.freeze(this.$slots.default)
},
methods: {
renderChildren() {
const children = this.$slots.default
const body = this.$el.contentDocument.body
const el = document.createElement('div') // we will mount or nested app to this element
body.appendChild(el)
const iApp = new Vue({
name: 'iApp',
store,
data() {
return {
children: Object.freeze(children)
}
},
render(h) {
return h('div', this.children)
}
})
iApp.$mount(el)
this.iApp = iApp
}
}
}
finally here is how data is passed to the PreviewComponent from the ConfirmationComponent:
export default {
name: 'ConfirmationComponent',
mounted() {
this.$store.commit('updateEmail', this.senderEmail)
this.$store.commit('updateTerm', this.term)
},
watch: {
'form.name'(val) {
this.updateIframe()
},
'form.email'(val) {
this.updateIframe()
}
},
methods: {
updateIframe() {
this.$store.commit('updateForm', this.form)
}
}
}
then lastly the actual PreviewComponent:
import styles from '../../../templates/styles'
export default {
name: 'PreviewComponent',
mounted() {
this.$store.commit('stylesChange', styles)
},
computed: {
redemption_url() {
return `${window.config.stitcher_website}/gift?code=`
},
custom_message() {
if (this.form.message) {
let div = document.createElement('div')
div.innerHTML = this.form.message
let text = div.textContent || div.innerText || ''
return text.replace(/(?:\r\n|\r|\n)/g, '<br>')
}
return null
},
form() {
return this.$store.state.form
},
term() {
return this.$store.state.term
},
senderEmail() {
return this.$store.state.senderEmail
}
}
}
hopefully this will help somebody.

Clear input VUE component data from parent

I'm a newbie of Vue, and I'm trying to simply clear the data of input component once I've submitted, but it seems I'm missing something, because since it's parent data is cleared, I still see the filled value of the input component.
Here is a living example.
I've set to the input child component v-model="title" from it's parent wrapper. Once I submit the data to the parent, I call addItem and in the end, I supposed to clear the data model just by clear it this.title = '', but I probably do something wrong on how to bind data from parent to child.
And above the code, starting from the parent component:
<template>
<form #submit="addItem" class="todo-insert">
<input-item
icon="create"
name="title"
placeholder="Add a ToVue item..."
v-model="title"
/>
<button-item tone="confirm" class="todo-insert__action">
Aggiungi
</button-item>
</form>
</template>
<script>
import ButtonItem from '#vue/Form/ButtonItem/ButtonItem.vue'
import InputItem from '#vue/Form/InputItem/InputItem.vue'
import uuid from 'uuid'
export default {
name: 'TodoInsert',
components: {
ButtonItem,
InputItem
},
data () {
return {
title: ''
}
},
methods: {
addItem (e) {
e.preventDefault()
const todo = {
id: uuid.v4(),
isComplete: false,
title: this.title
}
this.$emit('add-todo', todo)
this.title = ''
}
}
}
</script>
<style lang="scss" scoped src="./TodoList.scss"></style>
This is the child input component:
<template lang="html">
<label class="input">
<div v-if="label" class="input__label text-sans text-sans--label">
{{ label }}
</div>
<div class="input__row">
<input
:autocomplete="autocomplete"
:class="[hasPlaceholderLabel, isDirty]"
:name="name"
:placeholder="placeholder"
class="input__field"
type="text"
v-on:input="updateValue($event.target.value)"
v-on:blur="updateValue($event.target.value)"
>
<div v-if="placeholderLabel" class="input__placeholder text-sans text-sans--placeholder">
{{ placeholderLabel }}
</div>
<div v-if="icon" class="input__icon-area">
<icon-item
:name="icon"
/>
</div>
</div>
</label>
</template>
<script>
import IconItem from '../../IconItem/IconItem.vue'
export default {
name: 'InputItem',
props: {
autocomplete: {
type: String,
default: 'off'
},
icon: String,
label: String,
name: {
type: String,
default: 'input-text'
},
placeholder: String,
placeholderLabel: String
},
computed: {
hasPlaceholderLabel () {
return this.placeholderLabel ? 'input__field--placeholder-label' : ''
},
isDirty () {
// return this.$attrs.value ? 'input__field--dirty' : ''
return 'input__field--dirty'
}
},
methods: {
updateValue: function (value) {
this.$emit('input', value)
}
},
components: {
IconItem
}
}
</script>
<style lang="scss" src="./InputItem.scss"></style>
What am I missing?
Your child component is bound unidirectionally. It means that it can change the value, but does not receive any update from the parent component. To receive updates, you need to receive the property value in your child:
props: {
value: String
}
Then, you need to pass the value received to the input :
<input
:value="value"
:autocomplete="autocomplete"
:class="[hasPlaceholderLabel, isDirty]"
:name="name"
:placeholder="placeholder"
class="input__field"
type="text"
v-on:input="updateValue($event.target.value)"
v-on:blur="updateValue($event.target.value)"
>
Now the input should update when the parent component changes the value

Categories