nuxt vuex: do not mutate vuex store state outside mutation handlers - javascript

Why do I get this error:
Error [vuex] Do not mutate vuex store state outside mutation handlers
This happens when I call this component.
<template lang="pug">
.auth-popup
button.auth-popup__button-top.auth-popup__button-top_close(
type='button'
aria-label='Закрыть форму авторизации'
#click='$emit(`close`)'
)
h2.auth-popup__title Вход или регистрация
form.auth-popup__form(#submit.prevent='submitHandler')
input.auth-popup__fake-input(v-show='showFakeInput' aria-hidden='true' autocomplete='off' ref='fakeInput')
label.auth-popup__label(for='authLogin') Телефон
input#authLogin.auth-popup__input(
type='tel'
autocomplete='tel'
v-model='login'
#input='inputHandler'
ref='loginInput'
)
p.auth-popup__error(v-if='login && !isPhoneAuth') Телефон указан неверно
p.auth-popup__error(v-if='error' v-html='error')
p.auth-popup__timer(v-if='getCodeTimer' v-html='codeTimerMessage')
button.auth-popup__button-send(
type='submit'
:disabled='!isLoginLength || !isPhoneAuth || getCodeTimer || showPreloader'
)
span.auth-popup__button-inner(v-if='!showPreloader') Получить код
Preloader.auth-popup__preloader(:show='showPreloader' :color='`#ffffff`')
button.auth-popup__button-email(
type='button'
#click='$emit(`email`)'
) Войти по почте
</template>
<script>
import { mapActions, mapMutations, mapGetters } from 'vuex'
import { REGEXPS } from '~/assets/js/utils/constants/regexps';
import { MESSAGES } from "~/assets/js/utils/constants/messages";
import delay from '~/assets/js/utils/functions/promiseTimeout';
import Preloader from "~/components/modules/Preloader"
export default {
name: 'AuthPhone',
components: {
Preloader
},
data() {
return {
showFakeInput: false,
showPreloader: false,
login: '',
error: ''
}
},
computed: {
isPhoneAuth() {
return REGEXPS.FULL_PHONE_SYMBOLS.test(this.login);
},
isLoginLength() {
const phoneDigits = this.login.trim().replace(/\D/g, ``);
return phoneDigits.length > 9;
},
createPhoneValue() {
let phoneNumber = this.login;
if (phoneNumber.startsWith('8')) {
phoneNumber = '7' + phoneNumber.slice(1);
}
return `+${phoneNumber.replace(/\D+/g, '')}`;
},
...mapGetters({
getAuthResponse: 'authorization/getAuthResponse',
getCodeTimer: 'authorization/getCodeTimer',
codeTimerMessage:'authorization/codeTimerMessage'
})
},
methods: {
...mapActions({
authRequest: 'authorization/authRequest'
}),
...mapMutations({
startCodeTimer: 'authorization/startCodeTimer',
resetCodeTimer: 'authorization/resetCodeTimer'
}),
inputHandler() {
this.error = '';
if (this.getCodeTimer) {
this.resetCodeTimer();
}
},
async submitHandler() {
this.showPreloader = true;
const sendData = {
ident_method: `PHONE`,
login: this.createPhoneValue
};
await this.authRequest(sendData)
.then(() => {
this.showPreloader = false;
const data = this.getAuthResponse;
if (data.result) {
if (data.is_registered && !data.is_active) {
this.error = MESSAGES.ERROR.ACCOUNT_DEACTIVATED;
} else if (data.is_code_sended) {
this.startCodeTimer(30);
this.$emit('enter');
}
} else if (MESSAGES.ERROR[data.error]) {
this.error = MESSAGES.ERROR[data.error];
} else {
this.error = data.error;
}
});
},
},
mounted() {
if (this.getAuthResponse.login && this.getAuthResponse.ident_method === `PHONE`) {
this.login = this.getAuthResponse.login;
}
this.showFakeInput = true;
this.$nextTick()
.then(() => {
this.$refs.fakeInput.focus();
return delay(500);
})
.then(() => {
this.$refs.loginInput.focus();
this.showFakeInput = false;
});
},
}
</script>
The problem arises in this mutation - this.startCodeTimer (30);
Mutation file:
export default {
setAuthResponse(state, data) {
state.authResponse = data
},
setCodeResponse(state, data) {
state.codeResponse = data
},
setRegResponse(state, data) {
state.regResponse = data
},
setAuthCode(state, data) {
state.authCode = data
},
startCodeTimer(state, time) {
state.newCodeTimer = time
state.codeTimerId = setInterval(() => {
if (state.newCodeTimer) {
state.newCodeTimer--
} else {
clearInterval(state.codeTimerId)
}
}, 1000)
},
resetCodeTimer(state) {
state.newCodeTimer = 0
}
}
If I understand correctly, then the problem is here.
state.codeTimerId = setInterval(() => {
if (state.newCodeTimer) {
state.newCodeTimer--
} else {
clearInterval(state.codeTimerId)
}
}, 1000)
But so far there are no ideas how to solve it.

The problem was that state cannot be changed inside setInterval.
Solution: Create a mutation that will change the state and call this mutation inside setInterval.
Example:
setNewCode(state, count) {
state.newCodeTimer = count
},
startCodeTimer(state, time) {
state.newCodeTimer = time
state.codeTimerId = setInterval(() => {
if (state.newCodeTimer) {
this.commit('authorization/setNewCode', time--)
} else {
clearInterval(state.codeTimerId)
}
}, 1000)
},

Related

Vue component using axios to update a preference

I am trying to update a preference, example UI attached. The default is yes but a user should have the option to select no. I know I am a little way off but I just need some help identifying where I am going wrong, any help would be really appreciated.
Parent:
<CommunicationPreference
v-for="(communication, index) in communicationPreferenceType"
:key="index + communication.name"
:consent="communication.consent"
:name="communication.name"
#accept-consent="acceptConsent"
#decline-consent="declineConsent"
/>
methods: {
async acceptConsent() {
await this.$store.dispatch('account/updateCommunicationPreferences')
},
async declineConsent() {
await this.$store.dispatch('account/updateCommunicationPreferences')
},
}
CommunicationPreference.vue component:
<Button
:text="Yes"
:type="consent === true ? 'primary' : 'secondary'"
#clicked="acceptConsent"
/>
<Button
:text="No"
:type="consent !== true ? 'primary' : 'secondary'"
#clicked="declineConsent"
/>
methods: {
acceptConsent(consent) {
this.$emit('accept', consent === true)
},
declineConsent(consent) {
this.$emit('decline', consent === false)
},
},
Store:
async updateCommunicationPreferences({ commit, state }) {
const { communicationTypeName } = state.communicationTypeName
if (!communicationTypeName) {
return
}
try {
const response = await this.$axios.put(`/communication-consent/${communicationTypeName}`)
const { data: updatedCommunicationPreferences } = response.data
commit('SET_UPDATED_COMMUNICATION_PREFERENCES', updatedCommunicationPreferences)
} catch (error) {
commit('ADD_ERROR', { id: 'updateCommunicationPreferences', error }, { root: true })
}
},
As mentioned in the comments, the name of the method called is incorrect.
As mentioned by #qimolin, the values related to each option are not being passed to the function that saves it, this can be done by passing a value at calling the action.
methods: {
async acceptConsent() {
await this.$store.dispatch('account/updateCommunicationPreferences', { consent: true })
},
async declineConsent() {
await this.$store.dispatch('account/updateCommunicationPreferences', { consent: false })
}
or even that simplified with a single method
<CommunicationPreference
v-for="(communication, index) in communicationPreferenceType"
:key="index + communication.name"
:consent="communication.consent"
:name="communication.name"
#accept-consent="acceptConsent(true)"
#decline-consent="declineConsent(false)"
/>
methods: {
async updateConsent(consent) {
await this.$store.dispatch('account/updateCommunicationPreferences', { consent })
}
}
and that parameter must be captured on action
async updateCommunicationPreferences({ commit, state }, payload) {
const { consent } = payload // true or false. This is the value selected by the user.
const { communicationTypeName } = state.communicationTypeName
if (!communicationTypeName) {
return
}
try {
const response = await this.$axios.put(`/communication-consent/${communicationTypeName}`)
const { data: updatedCommunicationPreferences } = response.data
commit('SET_UPDATED_COMMUNICATION_PREFERENCES', updatedCommunicationPreferences)
} catch (error) {
commit('ADD_ERROR', { id: 'updateCommunicationPreferences', error }, { root: true })
}
},

Could I use method call from mounted function?

Could I use method call from mounted function?
in my code, I used this method.
mounted() {
this.initEvent();
this.getnewD(
$(function () {
$("#file-manager").dxFileManager({
name: "fileManager",
fileSystemProvider: customProvider,
currentPath: "Documents",
rootFolderName: "Root",
height: 450,
onErrorOcurred: function (e) {
debugger;
console.log(e);
},
permissions: {
create: true,
copy: true,
move: true,
delete: true,
rename: true,
},
customizeDetailColumns: (columns) => {
columns.push({
caption: "Creator",
dataField: "dataItem.creator",
});
return columns;
},
});
}));
},
And in my methods, I tried to used this methods to call mounted function.
But I got the customProvider is not a function.
So where has problem in my code?
methods: {
arr.forEach((item) => {
let tokens = item.path.replace(/^\/|\/$/g, "").split("/");
let current = tree;
for (let i = 0; i < tokens.length; i++) {
if (!current[tokens[i]]) {
current[tokens[i]] = {};
}
current = current[tokens[i]];
}
});
const parseNode = function (node) {
return Object.keys(node).map((key) => {
if (Object.keys(node[key]).length === 0) {
return {
isDirectory: false,
name: key,
};
}
return {
isDirectory: true,
name: key,
items: parseNode(node[key]),
};
});
};
let result = parseNode(tree);
var objectProvider =
new DevExpress.fileManagement.ObjectFileSystemProvider({
data: new_r,
});
var customProvider =
new DevExpress.fileManagement.CustomFileSystemProvider({
getItems: function (pathInfo) {
return objectProvider.getItems(pathInfo);
},
renameItem: function (item, name) {
if (item.name == "Parent1") {
console.log("error in custom provider");
throw {
errorId: 0,
fileItem: item,
};
console.log("error in custom provider");
} else return objectProvider.renameItem(item, name);
},
createDirectory: function (parentDir, name) {
if (parentDir.name == "Parent1") {
throw {
errorId: 0,
fileItem: item,
};
} else return objectProvider.createDirectory(parentDir, name);
},
deleteItem: function (item) {
console.log(item);
if (item.name == "Parent1") {
throw {
errorId: 0,
fileItem: item,
};
} else return objectProvider.deleteItems([item]);
},
moveItem: function (item, destinationDir) {
if (item.name == "Parent1") {
throw {
errorId: 0,
fileItem: item,
};
} else
return objectProvider.moveItems([item], destinationDir);
},
copyItem: function (item, destinationDir) {
if (item.name == "Parent1") {
throw {
errorId: 0,
fileItem: item,
};
} else
return objectProvider.copyItems([item], destinationDir);
},
});
let new_r = (self.fileSystemProvider = [
{
name: "Security Manager",
isDirectory: true,
items: result,
},
]);
}
I could got the data, but couldn't got some function and displayed the customProvider is not a function.
This has no proper function name in methods: { }
arr.forEach((item) => { ... }
Your methods need to be named!
You need something like that in your methods: object:
methods: {
myfunction(items) {
items.forEach((item) => {
...
}
}
Now you can call myfunction(arr) somewhere else in your script tags
mounted() works with plain js code inside because it is a named lifecycle hook. The methods: { } object however is not a function.

mutate vuex store state outside mutation handlers

I created a custom timer component, but I got errors about vuex store.
This component starts a timer when it's created and put into the store, then each minute it increments the time. If the person leaves the page like the timer is in the store, the time is saved and when the person comes back to the page, the timer resume by itself.
This is my component
<template>
<div>
<v-icon v-if="timer.visibility" :color="timeColor" #click="changeTimer">
{{ $icons.clock }}
</v-icon>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number,
required: true
},
id: {
type: Number,
default: null
},
type: {
type: String,
default: null
}
},
data() {
return {
timer: {
actif: false,
visibility: false,
timeOut: null,
dateDebut: null,
id: 0
}
}
},
computed: {
listeTimer() {
return this.$store.state.listeTimerPointage
},
timeColor() {
return this.timer.actif ? 'green' : 'red'
},
count: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
},
beforeDestroy() {
this.stopTimer()
},
created() {
let precedentTimer = null
if (this.id > 0) {
if (this.listeTimer.length > 0) {
precedentTimer = this.listeTimer.filter((f) => f.id === this.id)
}
} else {
this.timer.id = 0
}
this.startTimer(precedentTimer)
},
methods: {
// Start le timer
startTimer(precedentTimer) {
if (precedentTimer !== null) {
this.timer = precedentTimer
this.count = precedentTimer.count
} else {
this.count = 1
this.timer.dateDebut = new Date()
if (this.timer.timeOut === null) {
this.timer.actif = true
this.timer.visibility = true
this.timer.timeOut = setTimeout(() => {
this.timerBoucle()
}, 60000)
}
}
// this.listeTimer.push(this.timer)
this.$store.commit('addListeTimerPointage', this.timer)
},
// Arrete le timer
stopTimer() {
this.timer.actif = false
clearTimeout(this.timer.timeOut)
},
// Met en place la boucle toute les 1 minutes
timerBoucle() {
const now = new Date()
const diff = now - this.timer.dateDebut
this.count += Math.round(diff / 60000)
this.timer.dateDebut = new Date()
this.timer.timeOut = setTimeout(() => {
this.timerBoucle()
}, 60000)
},
// Modifie l'état du timer
changeTimer() {
this.timer.actif = !this.timer.actif
if (!this.timer.actif) {
clearTimeout(this.timer.timeOut)
} else {
this.timer.dateDebut = new Date()
this.timer.timeOut = setTimeout(() => {
this.timerBoucle()
}, 60000)
}
}
}
}
</script>
I indeed mutate a state, but I don't think I change the state directly
And this is the store:
addListeTimerPointage(state, data) {
state.listeTimerPointage.push(data)
},
deleteTimer(state, data) {
const newArray = state.listeTimerPointage.filter(
(item) => item.id !== data
)
state.listeTimerPointage = newArray
}
Thanks for your help

VueJS data not reactive mounted

I have an API which returns all the currency rate, i used a function getRate() on mounted but rate['usd'] is undefined, if i call the function again on that page it returns the actual data, i tried beforeCreated beforeMounted but they are not working, how to make the data reactive on load or am i doing something wrong?
<template>
<span v-text="rate['usd']"></span>
</template>
<script>
data() {
return {
rate: null
}
},
methods: {
getRate() {
this.$vs.loading()
this.$http.post('wallet/rate' ,[])
.then(response => {
for(let key in response.data.data.data){
this.rate[response.data.data.data[key].name] = response.data.data.data[key].value
}
this.$vs.loading.close()
})
.catch(error => {
this.$vs.loading.close()
})
},
},
mounted() {
this.getRate()
}
</script>
Does this work?
<template>
<span v-text="rate.usd"></span>
</template>
<script>
data() {
return {
rate: null
}
},
methods: {
getRate() {
const rate = {}
this.$vs.loading()
this.$http.post('wallet/rate' ,[])
.then(response => {
for(let key in response.data.data.data){
rate[response.data.data.data[key].name] = response.data.data.data[key].value
}
this.$vs.loading.close()
this.rate = rate
})
.catch(error => {
this.$vs.loading.close()
})
},
},
mounted() {
this.getRage()
}
</script>

Vue.js. Data property undefined

I'm trying to access my data property in my Vue.js component. Looks like I'm missing something obvious.
Here is a short version of my code. StoreFilter.vue is a wrapper for matfish2/vue-tables-2.
<template>
<store-filter :selected.sync="storeIds"></store-filter>
</template>
<script>
import StoreFilter from './Filters/StoreFilter';
export default {
components: {
StoreFilter
},
data() {
return {
options : {
requestFunction(data) {
console.log(this.storeIds); //undefined
return axios.get('/api/orders', {
params: data
}).catch(function (e) {
this.dispatch('error', e);
}.bind(this));
},
},
storeIds: [],
}
},
watch : {
storeIds(storeIds) {
this.refreshTable();
}
},
methods : {
refreshTable() {
this.$refs.table.refresh();
}
}
}
</script>
How to get storeIds from requestFunction?
Use a closure, see rewrite below.
data() {
let dataHolder = {};
dataHolder.storeIds = [];
dataHolder.options = {
requestFunction(data) {
// closure
console.log(dataHolder.storeIds); // not undefined
return axios.get('/api/orders', {
params: data
}).catch(function (e) {
this.dispatch('error', e);
}.bind(this));
}
}
return dataHolder;
}
I recommend using the created() way to handle this.
export default {
// whatever you got here
data () {
return {
options: {}
}
},
created () {
axios.get('/api/orders', { some: params }).then(response => this.options = response.data)
}
}

Categories