Vuejs doesn't update the DOM on Vuex mutation - vue.js

I use Vue3 with Quasar2 and I have a problem during mutation: the DOM isn't updated.
The code consists of a parent component, a list of child components (rendered by v-for). The data comes from a Vuex store via computed property.
The child components only represent data and emit an 'edit' event with the id of the represented data.
On the 'edit' event the parent component fills properties with the actual data a presents a dialog.
When the dialog closes the parent calls an action to store the data.
The action (and the following mutation) are executed normally. The Vuex store contains the correct data.
But the list isn't updated :(
If I add a new element to the data list (via push) then the list will be updated.
Cleared sources are the following:
Parent.vue:
<template>
<q-page class="flex flex-center" :key="mykey">
<q-list padding>
<child-component
v-for="d in data"
v-bind:key="d.id"
v-bind:data="data"
v-on:edit-time="onEdit"
>
</program>
</q-list>
<!-- EDITOR DIALOG -->
<q-dialog v-model="editing" #hide="onEditDone">
....
</q-dialog>
<script>
....
export default defineComponent({
name: "PageIndex",
created: function () {
this.loadData();
},
data: function () {
return {
editing: false,
....
},
};
},
components: { Child },
methods: {
...mapActions(["loadData", "updateData"]),
onEdit(id) {
...
this.editing = true; // <= show dialog
},
onEditDone() {
try {
...
this.updateData(data); // <== Vuex Action which updates an elem of the data list
} catch (e) {
...
}
},
},
computed: {
...mapState({
data: state => state.data
}),
}
});
</script>
state.js:
export default {
data: [],
}
getters.js:
const getters = {
data: (state) => {
return state.data;
},
};
export default getters;
mutations.js:
updateData: (state, data) => {
var idx = state.data.findIndex((element) => element.id == data.id);
state.data.splice(idx, 1, data);
},
actions.js:
async updateData(context, data) {
try {
await axios.post(`api/data?id=${data.id}`, data, {
headers: {
"Content-Type": "application/json",
},
});
context.commit("updateData", data)
} catch (err) {
console.log(err);
}
},

Related

Why does Vuetify Autocomplete not selecting the data being set?

I use Vuetify autocomplete as a reusable component to display a list of jobs in key/value pairs. In creating record, the component works fine but when editing where data should be filled in, the data has the value but not showing on the component.
JobDropdownSelector.vue:
................................................................................................................................................
<template>
<v-autocomplete
v-model="job"
label="Job Designation"
item-value="id"
item-text="name"
return-object
:items="jobs"
#change="onChange"
>
</v-autocomplete>
</template>
<script>
export default {
props: {
selectedJob: {
type: Object,
default: null
}
},
data() {
return {
jobs: [],
job: null
};
},
methods: {
getDataFromApi() {
axios.get("api/jobs")
.then(response => {
this.jobs = response.data.data;
})
.catch(error => {
console.log(error);
reject();
});
},
onChange(e) {
this.job = e;
this.$emit("onChange", e);
}
},
watch: {
selectedJob: {
deep: true,
immediate: true,
handler(newValue, oldValue) {
this.job = newValue;
}
}
},
mounted() {
this.getDataFromApi();
}
};
</script>
EditForm.vue:
................................................................................................................................................
<template>
<div>
<JobDropdownSelector
v-model="job"
:selectedJob="job"
#onChange="onChangeJob"
>
</JobDropdownSelector>
</div>
</template>
<script>
import JobDropdownSelector from "../components/JobDropdownSelector";
export default {
components: {
JobDropdownSelector
},
data() {
return {
job: null
};
},
methods: {
onChangeJob(e) {
this.job = e;
},
getInitialJob() {
axios.get("api/jobs/22").then(response => {
this.job = response.data.data;
});
}
},
mounted() {
this.getInitialJob();
}
};
</script>
Display
Console
It looks like you overriding job twice: on the onChangeJob method and via v-model on JobDropdownSelector.
You can try use input event to update modek instead of emitting additional event.
JobDropdownSelector.vue
methods: {
onChange(e) {
// You don't need set job because it's connected via v-model.
// this.job = e;
this.$emit("input", e);
}
}
This will cause that job in EditForm.vue should update automatically after dropdown change.

Vue2 create component based on data

I want to create a component based on ajax api response or data which include:
template
data
methods - there may be several methods
Remark: response or data is dynamic and it is not saved in file.
I have tried to generate and return result like :
<script>
Vue.component('test-component14', {
template: '<div><input type="button" v-on:click="changeName" value="Click me 14" /><h1>{{msg}}</h1></div>',
data: function () {
return {
msg: "Test Componet 14 "
}
},
methods: {
changeName: function () {
this.msg = "mouse clicked 14";
},
}
});
</script>
and do compile above code :
axios.get("/api/GetResult")
.then(response => {
comp1 = response.data;
const compiled = Vue.compile(comp1);
Vue.component('result-component', compiled);
})
.catch(error => console.log(error))
I got error on Vue.compile(comp1) -
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as
<script>, as they will not be parsed.
Thanks in advance
Your Api should return a JSON with every property required by a Vue component (name, data, template, methods), note that methods needs to be converted into an actual js function (check docs about that)
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
data() {
return {
apiComponent: { template: '<div>Loading!</div>' }
};
},
methods: {
loadApiComponent() {
setTimeout(() => {
this.buildApiComponent(JSON.parse('{"name":"test-component14","template":"<div><input type=\\\"button\\\" v-on:click=\\\"changeName\\\" value=\\\"Click me 14\\\" /><h1>{{msg}}</h1></div>","data":{"msg":"Test Componet 14 "},"methods":[{"name":"changeName","body":"{this.msg = \\\"mouse clicked 14\\\";}"}]}'));
}, 2000);
},
buildApiComponent(compObject) {
const {
name,
template,
data,
methods
} = compObject;
const compiledTemplate = Vue.compile(template);
this.apiComponent = {
...compiledTemplate,
name,
data() {
return { ...data
}
},
methods: methods.reduce((c, n) => {
c[n.name] = new Function(n.body);
return c;
}, {})
};
}
},
mounted() {
this.loadApiComponent();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component :is="apiComponent" />
</div>

Detect vuex state change to execute a method inside a nuxt layout

I am trying to show vuetify snackbar alert, once I completed a form submission inside a page or vue component. I use vuex store to manage alert type and message.
my-nuxt-app/store/alerts.js
export const state = () => ({
message: '',
type: ''
});
export const getters = {
hasAlert(state) {
return state.message !== '';
},
alertMessage(state) {
return state.message;
},
alertType(state) {
return state.type;
}
};
export const mutations = {
SET_ALERT(state, payload) {
state.type = payload.type;
state.message = payload.message;
}
};
export const actions = {
setAlert({commit}, payload) {
commit('SET_ALERT', payload);
},
clearAlert({commit}) {
commit('SET_ALERT', {});
}
};
And I created a nuxt plugin to access getters globally in my application.
my-nuxt-app/plugins/alert.js
import Vue from 'vue';
import {mapGetters} from 'vuex';
const Alert = {
install(Vue, options) {
Vue.mixin({
computed: {
...mapGetters({
hasAlert: 'alerts/hasAlert',
alertType: 'alerts/alertType',
alertMessage: 'alerts/alertMessage'
})
}
});
}
};
Vue.use(Alert);
Inside my AccountForm component submit method, I am dispatching my alert information to store like below.
my-nuxt-app/components/form/AccountForm.vue
...
methods: {
async submit () {
try {
await this.$axios.patch("/settings/profile", this.form);
this.$store.dispatch('alerts/setAlert', {
type: 'success',
message: 'You have successfully updated your information.'
});
} catch (e) {
}
}
},
...
}
...
And this AccountForm.vue component is a child component of profile.vue page which is obviously inside the pages folder of my project. And also I have extended the dashboard.vue layout to this profile.vue page and to the most of the pages inside my pages directory as a common layout. Hence, I added the snackbar component into dashboard layout to show a alert message whenever required.
my-nuxt-app/layouts/dashboard.vue
<template>
...
<v-snackbar
:timeout="snackbar.timeout"
:color="snackbar.color"
:top="snackbar.y === 'top'"
:bottom="snackbar.y === 'bottom'"
:right="snackbar.x === 'right'"
:left="snackbar.x === 'left'"
:multi-line="snackbar.mode === 'multi-line'"
:vertical="snackbar.mode === 'vertical'"
v-model="snackbar.show"
>
{{ snackbar.text }}
<v-btn flat icon dark #click.native="snackbar.show = false">
<v-icon>close</v-icon>
</v-btn>
</v-snackbar>
...
</template>
<script>
...
data: () => ({
snackbar: {
show: false,
y: 'top',
x: null,
mode: '',
timeout: 6000,
color: '',
text: ''
},
}),
computed: {
availableAlert: function () {
return this.hasAlert;
}
},
watch: {
availableAlert: function(alert) {
if(alert) {
this.showAlert(this.alertType, this.alertMessage);
this.$store.dispatch('alerts/clearAlert');
}
}
},
methods: {
showAlert(type, message) {
this.snackbar.show = true;
this.snackbar.color = type;
this.snackbar.text = message;
}
}
</script>
I am getting the alert message for the first time submission of the form and after that I have to reload the page and then submit to get the alert. Please enlighten me a way to detect the vuex state change and trigger showAlert method inside the dashboard.vue accordingly.
It's most likely the way you're checking hasAlert
Your clearAlert passes an empty object, your setAlert is trying to assign properties of that empty object, while your hasAlert is checking if it's an empty string.
If you change your clearAlert to:
clearAlert({commit}) {
commit('SET_ALERT', { message: '', type: '' });
}
That should fix your issue.

Iterating over a Vuex store object

I'm new to Vue.js and Vuex and trying out a sample app.
This is the scenario-
I have a store module for notifications which stores the notifications in an object with a given name as its key.
{
'message1': {
type: 'info',
message: 'This is an info message.',
isShown: true,
},
'message2': {
type: 'success',
message: 'This is a success message.',
isShown: true,
},
'message3': {
type: 'error',
message: 'This is an error message.',
isShown: true,
}
}
And this is my Vuex module that handles notification-
const state = {
notifications: {},
};
const mutations = {
setNotification(state, { message, type, name }) {
state.notifications[name] = {
message,
type,
isShown: true,
}
},
removeNotification(state, name) {
delete state.notifications[name];
}
};
const actions = {
async showNotification(context, options) {
await context.commit('setNotification', options);
},
async removeNotification(context, name) {
await context.commit('removeNotification', name);
}
}
const getters = {
isNotificationShown: (state, getters) => {
return getters.getNotificationMessageList.length > 0;
},
getNotificationMessageList: state => {
return state.notifications;
},
}
export default {
state,
actions,
mutations,
getters,
}
And this is my component-
<template>
<div v-if="isShown">
<div v-for="(notice, name, index) in notificationMessageList" :key="name">
{{ index }} - {{ notice.type }} - {{ notice.message}}
</div>
</div>
</template>
<script>
export default {
computed: {
isShown() {
return this.$store.getters.isNotificationShown;
},
notificationMessageList() {
return this.$store.getters.getNotificationMessageList;
},
},
};
</script>
I checked with the Vue Development tool and found that the store does get updated and so does the component with the notification messages that I'm passing to the store. But the component is not being rendered. But if I use the same data by hardcoding it in the component, it works.
I'm not sure if this is the right way to connect the Vuex store to a component.
It's Vue reactivity problem. You need to update the reference to make Vue reactive. You can use JSON.parse(JSON.stringify()) or use ES6 syntax:
const mutations = {
setNotification(state, { message, type, name }) {
state.notifications = {
...state.notifications,
[name]: {
message,
type,
isShown: true
}
}
},
removeNotification(state, name) {
const newNotifications = {...state.notifications}
delete newNotifications[name]
state.notifications = newNotifications
}
};

How can i have access to the dispatch event from the created method in vue?

I'm trying to have access to the data from the component, i can have access to it using the template but i actually want to be able to have access to the dispatch users details from the created method.
How can i have access to it?
<template>
<div>
<input type='text' class='e_pearl ep_pearl form-control' placeholder='Perle..' autoComplete='false' autoFocus spellCheck='false' :value='user.pearl_color' >
</div>
</template>
<script>
var colors = '#006064';
export default {
mixins: [userMixin],
components: {
Sidebar,
'swatches-picker': Swatches
},
methods: {
created(){
let {
session: { email },
$store: { dispatch }
} = this
dispatch('userDetails', email)
console.log(this.$data.colors); // access to data from created
},
data () {
return {
colors
}
},
mounted(){
$('.ep_password').focus()
}
}
</script>
I tried:
console.log(this.$store.state.user);
{ob: Observer}
session
:
(...)
userDetails
:
(...)
module
import actions from '../actions/user-a'
import $ from 'jquery'
let d = $('.data')
export default {
state: {
session: {
id: d.data('session'),
email: d.data('email')
},
userDetails: {}
},
mutations: {
USER_DETAILS: (state, payload) => state.userDetails = payload,
},
actions,
}
action
import { post } from 'axios'
export default {
userDetails: async (context, payload) => {
console.log(payload);
let { data } = await post('/api/get-details', { email: payload })
context.commit('USER_DETAILS', data)
},
}
If i try console.log(this.$store.state.userDetails);
Data are not available however i can see them from this.$store.state.user
But no luck!