Managing data in Vuex by updating it from a child component using $emit

I'm currently working on a Vue app that has the capability to randomize a title and subtitle or allow users to manually edit these values using a custom input component. The challenge I'm facing is updating the parent component and state to reflect the customized inputs when saved, as I keep encountering an "undefined" error for both the title and subtitle. Despite successfully emitting updated headings from the randomizer and child component, I am struggling to implement this functionality.

The main goal of this coding challenge is to send the new values back to the store and display the custom inputs while still allowing the option to use the randomizer. Any guidance on what I might be missing or doing wrong would be greatly appreciated, especially since I have been extensively studying Vuex and Vue2 for 3 days with only 2 months of practical experience with 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="subtitle">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 }, payload ) {
      commit('UPDATE_TITLE', payload.title || null);
      commit('UPDATE_SUBTITLE', payload.subtitle || null);
    },
  },
  modules: {
  },
});

Answer №1

After testing your code in my local development environment, it became apparent that several modifications were necessary to enhance its functionality. Below is the revised 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 = chooseRandomWords();
            commit('UPDATE_TITLE', newTitle);
        },
        randomizeSubtitle({ commit }) {
            const newSubtitle = chooseRandomWords();
            commit('UPDATE_SUBTITLE', newSubtitle);
        },
        randomizeTitleAndSubtitle({ dispatch }) {
            dispatch('randomizeTitle');
            dispatch('randomizeSubtitle');
        },
        updateTitleAndSubtitle({ commit }, userInput) {
            console.log(userInput);
            commit('UPDATE_TITLE', userInput.title);
            commit('UPDATE_SUBTITLE', userInput.subtitle);
        },
    },
    modules: {
    },
});

Additionally, here is the updated 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"
      />

      <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>
import CustomInput from '../components/CustomInput.vue';
import { mapActions } from 'vuex';

export default {
  name: 'Parent',
  components: {
    CustomInput,
  },
  data() {
    return {
      editMode: false,
      temporaryTitle: "",
      temporarySubTitle: ""
    };
  },

  computed: {
    title: {
      get: function () {
        return this.$store.state.title;
      },
      set: function (newValue) {
        this.$store.commit('UPDATE_TITLE', newValue);
      }
    },
    subtitle: {
      get: function () {
        return this.$store.state.subtitle;
      },
      set: function (newValue) {
        this.$store.commit('UPDATE_SUBTITLE', newValue);
      }
    },
  },
  methods: {
    ...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) {
      this.title = event;
    },
    myFuncSubTitle(event) {
      this.subtitle = event;
    },
    onSave(v) {
      this.editMode = false;
      const payload = {
        title: this.title,
        subtitle: this.subtitle,
      };
      this.updateTitleAndSubtitle(payload);
    },
  },
  created() {
    this.randomizeTitleAndSubtitle();
  },
};
</script>

Lastly, here is the revised code for the 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)"
    />

    <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() {
        return this.title;
      },
      set(title) {
      },
    },
    inputSubtitle: {
      get() {
        return this.subtitle;
      },
      set(subtitle) {
      },
    },
  },
};
</script>

<style scoped>

</style>

Several adjustments have been made to the codebase, including renaming mapActions actions in accordance with defined names in the "store" and implementing a setter for computed properties. It would be beneficial to review Vue and Vuex documentation, particularly focusing on custom events, computed setters, and Vuex actions if further clarification is needed.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Conflict between iOS 7+ swipe back gesture and stateChange animations

When transitioning between states in an AngularJS application, I utilize CSS animations to add visual appeal to the view change. This may involve applying fades or transforms using classes like .ng-enter and .ng-leave. In iOS 7+, users can swipe their fin ...

Is it possible to create a compound editor within a cell in SlickGrid that contains two date fields

Currently, I am implementing SlickGrid with jQuery and I am interested in incorporating a compound editor within a cell similar to example 3a mentioned here. However, instead of two text fields, I would like to have two date fields. Although Example 3 dem ...

Copy the content of one input field into another field at the same time

I have encountered an issue that seems simple, yet the proposed solutions have not been effective. Most suggestions involve using jQuery, but Bootstrap 5 has shifted away from jQuery. I am looking for a solution using JavaScript or HTML only. The problem ...

Vue js version 2.5.16 will automatically detect an available port

Every time I run the npm run dev command in Vue.js, a new port is automatically selected for the development build. It seems to ignore the port specified in the config/index.js file. port: 8080, // can be overwritten by process.env.PORT, if port is in u ...

What are the solutions for fixing a JSONdecode issue in Django when using AJAX?

I am encountering a JSONDecodeError when attempting to send a POST request from AJAX to Django's views.py. The POST request sends an array of JSON data which will be used to create a model. I would greatly appreciate any helpful hints. Error: Except ...

Modifying the CSS class of an element does not produce the desired effect after altering its styles using JavaScript

html: <input id="myinput" class="cinput" type="image" src="http://www.foodwater.org.au/images/triple-spiral-3-small-button.jpg"/> <br><br><br><br> <button id="s">set to visible class</button> <button id="h"> ...

Guide on inserting a MUI datepicker string value into the object state

Currently, I am in the process of developing a todo-list application that includes fields for description, priority, and date. To capture the priority and description inputs, I utilize TextFields which trigger the {inputChanged} function. Within this funct ...

Creating a dynamic hyperlink variable that updates based on user input

I am attempting to create a dynamic mailto: link that changes based on user input from a text field and button click. I have successfully achieved this without using the href attribute, but I am encountering issues when trying to set it with the href attr ...

Is there a way to dynamically update the content of <li> tag with values from an array every x seconds

I am trying to create a li list using jQuery that will update every 2 seconds with new content, similar to game news updates. However, I'm encountering an issue during the first cycle where it skips the second item in the array. The subsequent cycles ...

Invalid content detected in React child element - specifically, a [object Promise] was found. This issue has been identified in next js

Why am I encountering an error when I convert my page into an async function? Everything runs smoothly when it's not an async function. The only change is that it returns a pending object, which is not the desired outcome. This is how data is being f ...

Error in Vue SSR and Gridsome: Uncaught ReferenceError: window is not defined

While attempting to integrate Hotjar with Gridsome, I encountered an issue when using the code snippet below in src/main.js: import Hotjar from 'vue-hotjar'; Vue.use(Hotjar, { id: 'HOTJAR_ID' }); An error message was di ...

Step-by-step guide for importing a JSON file in React typescript using Template literal

I am facing an error while using a Template literal in React TypeScript to import a JSON file. export interface IData { BASE_PRICE: number; TIER: string; LIST_PRICE_MIN: number; LIST_PRICE_MAX: number; DISCOUNT_PART_NUM: Discout; } type Discoun ...

Using the Spread Operator to modify a property within an array results in an object being returned instead of

I am trying to modify the property of an object similar to this, which is a simplified version with only a few properties: state = { pivotComuns: [ { id: 1, enabled : true }, { id: 2, enabled : true ...

Is it possible to update the version of NPM?

Having an issue with installing packages for my React-Native project due to a NPM version error. How can I upgrade it? Currently using version 4 ...

Avoiding HTML in JavaScript: Tips and Tricks

My application generates numerous elements dynamically based on server data in JSON format. This process involves embedding a significant amount of HTML directly within my JavaScript code, leading to pollution and making it increasingly challenging and l ...

Retrieving the value from a concealed checkbox

I have been searching everywhere, but I can't seem to find a solution to this particular issue. There is a hidden checkbox in my field that serves as an identifier for the type of item added dynamically. Here's how I've set it up: <inpu ...

How to format a large number using the toFixed() method in a VueJS template

I have a reward value that consists of a number with 9 decimal places (which is necessary for calculations). However, I want to display only 4 decimal places when using a VueJS template. <template> <div class="mb-2">Reward: {{ myAccc ...

How come useEffect runs only once even when multiple states in the dependency array of useEffect change simultaneously?

<div onClick={() => { updateValue1((x: number) => x + 1); updateValue2((x: number) => x + 3); }} > one? two? </div> const [value1, updateValue1] = useState(1); const [value2, updateValue2] = useState(1 ...

Achieving proper variable-string equality in Angular.js

In my Angular.js application, I am utilizing data from a GET Request as shown below. var app = angular.module('Saidas',[]); app.controller('Status', function($scope, $http, $interval) { $interval(function(){ ...

Unlock the potential of AngularJS services with this innovative design strategy

I am currently developing an AngularJS client application that will communicate with a REST server. To handle the interaction between the client and server, I have decided to use the $resource abstraction from AngularJS. Each resource is being written as ...