Unusual occurrences of Vuex mutations within an Electron application utilizing electron-vue framework

Currently, I am in the process of developing a straightforward task management application using Vue and Electron. My framework is reliant on the electron-vue boilerplate with Vuex store integration. The app allows users to add new items to a list (and modify existing ones) through a modal interface. Once the user input is collected by the modal, it is forwarded to a store action which triggers a mutation to update the store data and append a new item to the list array.

The hierarchy goes as follows: LayerItem is a child component of Layer, which in turn is a child of LayerMap. Data from the store is accessed within the parent LayerMap component and passed down to children components via props.

To replicate the issue described: Create a new item using showEditItemDialog feature in the Layer component. As part of the SAVE_LAYER_ITEM mutation process, a new ID is generated and assigned to the newly created item. Subsequently, the updated item is appended to the layer.items array. Although the UI reflects this change and displays the correct item.text, there seems to be a discrepancy in the displayed item.id. A console.log statement within the mutation logs an ID different from the one seen in the UI rendering of the LayerItem component where <p>{{ item.id }}</p> resides. Consequently, attempting to edit or update a recently added item results in a mutation creating a new item instead of updating the intended one, as the received ID cannot be located within the store array.

I understand that the details provided involve significant code snippets. However, I have made efforts to trim down unnecessary sections. In the example below, a new item "test" has been created, highlighting the disparity between the stored ID and the displayed ID.

Screenshot of Terminal logs https://i.sstatic.net/3sQ7k.png

Screenshot of DevTools console https://i.sstatic.net/k2tD4.png

Screenshot of Vue DevTools store https://i.sstatic.net/hcrOc.png

Screenshot of the UI https://i.sstatic.net/ozXJi.png

LayerMap.vue

// 'layers' is computed property fetching data from the store
        <draggable
          v-model="layers"
          v-bind="getDragOptions"
        >
          <Layer v-for="(layer, index) in layers" :key="index" :layer="layer"></Layer>
        </draggable>
        <DetailsModal></DetailsModal>

// Inside computed
  computed: {
    layers() {
      return this.$store.getters.allLayers
    }
  }

Layer.vue

// 'layer' being passed from parent as prop
     <span primary-focus @click="showEditItemDialog">Add Item</span> 
     <draggable v-model="items" v-bind="dragOptions" class="items">
        <LayerItem v-for="item in items" :item="item" :layer="layer" :key="item.id"></LayerItem>
      </draggable>

// 'items' is a computed property
    items: {
      get() {
        return this.layer.items
      }
    }

// Function handling 'Add Item' click event and emitting signal to be caught by DetailsModal.vue
  methods: {
    showEditItemDialog() {
      let payload = {
        layer: this.layer,
        item: {
          id: '',
          text: ''
        }
      }
      this.$bus.$emit('item-editing', payload)
    }
  }

LayerItem.vue

// Layer Item Component
  <div class="layer-item" @click.prevent="startEditing">
    <div class="item-body">
      <p>{{ this.item.text }}</p>
      <p>{{ item.id }}</p>
    </div>
  </div>

// Event triggered on click providing details of the clicked layer item
  methods: {
    startEditing() {
      let payload = {
        layer: this.layer,
        item: {
          id: this.item.id,
          text: this.item.text
        }
      }
      this.$bus.$emit('item-editing', payload)
    }
  }
}

DetailsModal.vue

// 'editLayerForm' stores the layer item id and text
      <p>{{editLayerForm.id}}</p>
      <div class="bx--form-item">
        <input
          type="text"
          v-model="editLayerForm.text"
        />
      </div>

// Within <script>, event is intercepted and handled; 'editLayerForm' gets updated with payload information
  mounted() {
    this.$bus.$on('item-editing', this.handleModalOpen)
  },
  methods: {
    handleModalOpen(payload) {
      this.layer = payload.layer
      this.editLayerForm.id = payload.item.id
      this.editLayerForm.text = payload.item.text
      this.visible = true
      console.log('editing', payload)
    },
    handleModalSave() {
      let payload = {
        layerId: this.layer.id,
        item: {
          id: this.editLayerForm.id,
          text: this.editLayerForm.text
        }
      }
      console.log('save', payload)
      this.$store.dispatch('saveLayerItem', payload)
    }
  }

Store.js

const actions = {
  saveLayerItem: ({ commit }, payload) => {
    console.log('action item id', payload.item.id)
    commit('SAVE_LAYER_ITEM', payload)
  }
}

const mutations = {
  SAVE_LAYER_ITEM: (state, payload) => {
    let layer = state.map.layers.find(l => l.id === payload.layerId)
    let itemIdx = layer.items.findIndex(item => item.id === payload.item.id)
    console.log('mutation item id', payload.item.id)

    if (itemIdx > -1) {
      // For existing item
      console.log('update item', payload.item)
      Vue.set(layer.items, itemIdx, payload.item)
    } else {
      // For new item
      payload.item.id = guid()
      console.log('save new item', payload.item)
      layer.items.push(payload.item)
    }
  }
}

Answer №1

Exploring the realm of building an Electron app for the first time was quite a journey, but I eventually reached my destination! :)

Each Electron application consists of at least two processes - the main process, which handles opening browser windows, and the renderer process, where your Vue app operates. The location where console.log output appears depends on which process triggered it - when called from the main process, it only shows up in the terminal window (used during development mode), while log messages from the renderer process are visible exclusively in Dev Tools.

Interestingly, logs from mutations manifest in both locations, suggesting that the code is active in multiple processes. How can this be?

It seems that the electron-vue template offers the option to utilize vuex-electron, specifically leveraging its createSharedMutations plugin. This feature enables the sharing of a single Vuex store among the main process and all renderer processes (although each process technically maintains its own store, state synchronization occurs). The process unfolds as follows:

  1. You trigger an action (in the renderer process)
  2. The action is intercepted within the renderer process (explaining why no action logs appear in Dev Tools) and signals the main process to carry out the action instead.
  3. If the action executed by the main process results in a mutation, the mutation logic is processed within the main process (as shown in the initial screenshot with logs from the Terminal - note the empty ID) before the payload (now tagged with a freshly generated id A) gets serialized into JSON (see ipc-renderer) and relayed to all renderer processes for executing the same mutation (ensuring store coherence). Consequently, your mutation undergoes a second execution (depicted in the subsequent screenshot with logs from DevTools) - the item already possesses an id assigned (A), though it's absent from the item list prompting your code to assign a new id (B) and append it to the collection.
  4. id B materializes on the screen
  5. Subsequently, if you initiate edits and call upon an action to save, the sequence outlined in step 3 recurs; however, this time the mutation being executed in the main process encounters the item with id B (which isn't part of its items collection). Thus, it assigns a fresh id (C, overwriting B), causing the mutation executed in the renderer process to encounter an item bearing id C (also missing from its collection)...and so forth

The solution lies in disabling the createSharedMutations plugin in your store configuration file (typically located at /renderer/store/index.js). Should you require synchronized storage across the main process and renderer processes, mutation rewriting becomes imperative...

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

Express.js fails to redirect to the sign-in page after successfully retrieving the username from the MySQL database

I have encountered an issue while trying to retrieve the username from a MySQL database. The code I am using successfully retrieves the username, but when an error occurs, instead of redirecting to /signin, it redirects to /admin. Adding res.redirect(&ap ...

What is the best way to iterate through a JSON object and display its values?

When I receive JSON data from an API, it looks like this: callback({"Message":"","Names”:[{“id”:”16359506819","Status":"0000000002","IsCurrent":true,"Name":"JAKE"," ...

Tips for efficiently utilizing a Vue.js component with numerous props and slots

Within a vuejs application, there exist three components - TopClicks.vue, TopImpressions.vue, TopCtr.vue. Each of these components utilizes vue-good-table for rendering a similar table with varying sorting techniques: ../components/TopClicked.vue (around ...

"Learn the technique of adding a comma after each object and transforming it into an array using JavaScript in React JS

Is there a way to add commas after each object and transform it into an array? Below is the response data: {"url":"example.com/john","age":"32"}{"url":"example.com/mike","age":"42& ...

How can we efficiently generate ReactJS Router for Links and seamlessly display a unique page for each Link?

Currently, I have an array of objects named titleUrl, which contains titles and URLs retrieved from an API. To display these as links on the sidebar, I am utilizing a custom component called MenuLink. The links are generated by iterating over the keys in t ...

Ways to access configuration settings from a config.ts file during program execution

The contents of my config.ts file are shown below: import someConfig from './someConfigModel'; const config = { token: process.env.API_TOKEN, projectId: 'sample', buildId: process.env.BUILD_ID, }; export default config as someCo ...

Sort the collection of words by their corresponding suffixes

Looking to use JavaScript to compare two lists and extract words from WORDLIST that end with the characters in END (not in the middle of the word). I am also open to using jQuery for this task. var WORDLIST = ['instagrampost', 'facebookpost& ...

Oops! TypeScript error TS2740: The type 'DeepPartial<Quiz>[]' is currently missing key properties from type 'Question': id, question, hasId, save, and a few more

I'm struggling to resolve this error. Can anyone provide guidance on what needs to be corrected in order for this code to function properly? import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm& ...

Change the icon switch from fas fa-lock-open to fas fa-lock by adding an event listener for a click action

let lockIcon = document.createElement("i"); lockIcon.setAttribute("class", "fas fa-lock-open"); lockIcon.setAttribute("id", color + "lock"); Is there a way to toggle between the icons fas fa-lock-open and fas fa-lock when clicking using a ...

How is it possible that my array becomes reversed without any intervention on my part?

As I work on developing a website, I encountered a puzzling issue with the calculations inside the router. Despite initializing the array tomato with the desired values, it seems that the values get reversed just before rendering the page. Here is the code ...

405 Method Not Allowed - Compatible with all devices except for iOS Safari

Recently, I've been working on an application using AngularJS to track and submit statistics to a Google Apps Script for processing. While everything functions flawlessly on my computer in both Chrome and Firefox, encountering errors on the iPad has b ...

Avoid insecurely assigning an any value in TypeScript

Take a look at this code snippet: const defaultState = () => { return { profile: { id: '', displayName: '', givenName: '', }, photo: '', } } const state = reactive(defaultState() ...

How to utilize Node.js to send a document to Amazon S3

I recently wrote a function to upload files to S3 using the 'aws-sdk' in JavaScript, but I encountered an issue. When I replaced Body with Body: "test", the function executed successfully and I could see the content 'test' in my S3 buc ...

I am currently transferring cross-site forgery tokens through jQuery strings. However, on the subsequent page, I create a fresh token which means their original tokens will no longer align

Alright, so I've been storing the tokens in a session: Session::get('token', 'randomtokenstringhere'); Every time a form is submitted, whether successfully or not, I generate a new token and update their session token. But let&ap ...

The res.send() function is being executed prior to the async function being called in express.js

My current project involves creating an API that returns epoch time. I am using an express.js server for this, but the issue arises when the res.send() function is called before the getTimeStamp() function finishes executing. I tried looking up a solution ...

Integrating data between Java and JavaScript within the Wikitude platform is essential for leveraging AR.RelativeLocation

After passing an integer from Java to JavaScript, I am attempting to use the value to adjust the altitude of an object. I included the variable in RelativeLocation var location = new AR.RelativeLocation(null, 8, 0, a); The issue arises when it disregards ...

Leveraging Ajax with Google Analytics

Currently, I am working on a website that utilizes Ajax calls to update the main content. In order to integrate Google Analytics tracking code using the async _gaq method, I need to push a _trackPageview event with the URI to _gaq. There are two approaches ...

Who gets the callback when onreadystatechange is triggered in a single-threaded JavaScript environment?

Having recently delved into the world of JavaScript, I've come across the fact that it is single-threaded. My initial assumption was that when making an asynchronous request, a separate thread would be started to monitor the server's response. Ho ...

Tips for creating a fixed element with ScrollTrigger and GSAP

How can I prevent the right div from jumping on top of the left div when using a scroll trigger to make the left div's position fixed? gsap.registerPlugin(ScrollTrigger); const tlfour = gsap.timeline({ scrollTrigger: { trigger: ".ma ...

Unable to show the name of the chosen file

After customizing the input [type = file], I successfully transformed it into a button with a vibrant green background. Here is the code snippet that enabled this transformation: <style> #file { height:0px; opacity:0; } ...