I have the following Vuex store here:
const store = new Vuex.Store({
state: {
renderedBlocks: []
},
getters: {
asString: (state) => () => {
return JSON.stringify(state.renderedBlocks);
},
block: (state) => (k) => {
return state.renderedBlocks[k];
}
},
mutations: {
updateBlock(state, data) {
if (!state.renderedBlocks[data.id]) { return; }
state.renderedBlocks[data.id].params = data.params;
state.renderedBlocks[data.id].content = data.content;
},
updateBlockIndex(state, data) {
var s = state.renderedBlocks[data.id];
s.content[data.index] = data.value;
state.renderedBlocks[data.id] = s;
}
}
});
This state keeps track of all the "blocks" in a Wisiwyg editor (e.g. Heading, Image, Paragraph, etc).
In order to send all this when the form is submitted, I need to pass this data to json and assign it to a hidden text field. According to Vue devtools, the store updates correctly, but the "asString" getter doesnt update the JSON.
This is where all the components for all registered blocks (meaning the visible ones in the editor) are rendered:
<template>
<div class="zg-content">
<component v-for="(block, c) in $store.state.renderedBlocks" :is="block.component" :key="c"></component>
</div>
</template>
This is where the output happens:
<template>
<input type="hidden" :name="name" :value="asString">
</template>
<script>
export default {
computed: {
asString() {
return this.$store.getters.asString();
}
}
};
</script>
And this is where an Update of one block might occur:
<template>
<ul ref="input">
<li :key="key" v-on:keydown.enter="addElement(key, $event)" v-for="(point, key) in innerContent" contenteditable="true" v-html="point.content" @blur="updateContent(key, $event)"></li>
</ul>
</template>
<script>
export default {
props: {},
computed: {
params: {
set(v) {},
get() { return this.$store.getters.block(this.$vnode.key).params; }
},
innerContent: {
set(v) {},
get() { return this.$store.getters.block(this.$vnode.key).content; }
}
},
methods: {
addElement(index, event) {
var self = this;
event.preventDefault();
this.innerContent.splice(index+1, 0, {content: ''});
this.$store.commit('updateBlock', {
id: this.$vnode.key,
params: this.params,
content: this.innerContent
});
},
updateContent(key, event) {
this.$store.commit('updateBlockIndex', {
id: this.$vnode.key,
index: key,
value: {'content': event.target.innerHTML }
});
}
}
};
</script>
So, when I add a new element or modify an existing one, the blur event of the 'li' triggers. My state is updated currectly - meaning the Element is visible with {'content': 'NewContent'} in the store. But the JSON in the hidden input is still the old one.
I figured out, that when I create a "private" version of all the list elements right inside of my listing component, this is working as expected. But that's not really the "Vuex way" and in strict mode I also get a lot of errors.
From the docs:
Due to limitations in JavaScript, Vue cannot detect the following changes to an array: When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
You can use splice
or Vue.set
to overcome this because they are both detectable. For example, in updateBlockIndex
by replacing the last line with this:
state.renderedBlocks.splice(data.id, 1, s);
or
Vue.set(state.renderedBlocks, data.id, s);
If you use Vue.set
, you'll also need to:
import Vue from 'vue'
You should similarly do this in updateBlock
. I'm guessing the reason it "works" now is because of the splice
to innerContent
.