Reputation: 67
I'm trying to build a simple task management app with Vue and Electron. My setup is based on the electron-vue boilerplate with Vuex store. The user can add new items to the list (and edit existing items) through a modal. The modal sends the information to a store action which then calls a mutation to update the store and push a new item to the list item array.
This is the setup: LayerItem
is a child of Layer
which is a child of LayerMap
. Data is received from the store within the parent LayerMap
component and then provided to children via props.
Recreating the issue: Create a new item via the showEditItemDialog
in Layer
component. Within the SAVE_LAYER_ITEM
mutation, a new ID will be created and assigned to that new item. After that, the new item will be pushed to the layer.items
array. The UI will be updated and the created item is visible. item.text
is displayed correct. The item.id
however is different. I included a console.log
within the mutation. The logged id doesn't match the id displayed in the UI within the LayerItem
component here <p>{{ item.id }}</p>
. As a result, when trying to edit/update a new item after it has been created, instead of updating the existing item, the mutation will create a new item since the ID received by the modal can't be found in the store array.
I know it's a lot of code, I tried to remove as much unnecessary code as possible. In the example below, I created a new item "test" and you can see that the stored ID doesn't match the ID displayed in the UI.
Screenshot from the Terminal logs
Screenshot from the DevTools console
Screenshot from Vue DevTools store
LayerMap.vue
// 'layers' is a computed property and gets 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' gets 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 to handle 'Add Item' click and send event which will be handled 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 will be sent on click with layer item details as parameter
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' contains layer item id and text
<p>{{editLayerForm.id}}</p>
<div class="bx--form-item">
<input
type="text"
v-model="editLayerForm.text"
/>
</div>
// Inside <script>, event is received and handled, 'editLayerForm' will be 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)
}
}
}
Upvotes: 1
Views: 452
Reputation: 37913
Never did build Electron app before so it took me some time to dig deep enough but I think I got it! :)
Every electron app have at least 2 process - main (responsible for opening browser window) and renderer (where your Vue app runs). If you use console.log
in your code, where the output shows depends on which process called it - console.log
called from main process shows up only in the terminal window (used to start the app in dev mode), console.log
called from renderer process shows up only in Dev Tools.
But logs from your mutations appear in both! Which means the code must be running in both processes, right ? But how?
Well it seems, electron-vue template has an option (you had to switch on when setting up the project) to use vuex-electron, particularly it's createSharedMutations
plugin. It can be used to share same Vuex store between main process and all renderer processes (technically each process have its own store but state is synced). It works something like this:
id
A) is serialized into JSON (see ipc-renderer) and passed to each renderer processes to execute same mutation (hence keeping all stores in sync). Here your mutation is executed 2nd time (2nd screenshot with logs from DevTools) - item
has id
already assigned (A) but it's not in the list of items so your code assigns new id
(B) and push it into collection.id
B is rendered on screenid
B which is not in its collection of items. So it assigns new id
(C overwriting B) so mutation executing in renderer process again sees item with id
C which is not in collection....and so onSolution is obviously to disable createSharedMutations
plugin in your store config (should be in /renderer/store/index.js
). If you really need store synchronised across main process/renderer processes, you need to rewrite your mutations...
Upvotes: 2