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 >
<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: {
},
});
CodePudding user response:
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);
},
},
// ...
}
CodePudding user response:
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 >
<!-- <span >Title:</span> {{ title }} <br>
<span >Subtitle:</span> {{ subtitle }}
<hr> -->
<div v-if="!editMode" >
<div >
<span >Title: </span>{{title}}
</div>
<div >
<span >Subtitle: </span>{{subtitle}}
</div>
<div >
<button id="randomize-button" @click="randomizeTitleAndSubtitle">
Randomize
</button>
<button id="edit-button" @click="onEdit">Edit</button>
</div>
</div>
<div v-else >
<CustomInput
:value="{ title, subtitle }"
@input="v => onEdit(v)"
/>
<div >
<button id="cancel-button" @click="onCancel">Cancel</button>
<button id="save-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: {
},
});