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: { }, });
Advertisement
Answer
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: { }, });