Home > Blockchain >  Vuex Action committing mutation
Vuex Action committing mutation

Time:03-07

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

  • Related