I am using vuex
and vuejs 2
together.
I am new to vuex
, I want to watch a store
variable change.
I want t
As mentioned above it is not good idea to watch changes directly in store
But in some very rare cases it may be useful for someone, so i will leave this answer. For others cases, please see @gabriel-robert answer
You can do this through state.$watch
. Add this in your created
(or where u need this to be executed) method in component
this.$store.watch(
function (state) {
return state.my_state;
},
function () {
//do something on data change
},
{
deep: true //add this if u need to watch object properties change etc.
}
);
More details: https://vuex.vuejs.org/api/#watch
Create a Local state of your store variable by watching and setting on value changes. Such that the local variable changes for form-input v-model does not directly mutate the store variable.
data() {
return {
localState: null
};
},
computed: {
...mapGetters({
computedGlobalStateVariable: 'state/globalStateVariable'
})
},
watch: {
computedGlobalStateVariable: 'setLocalState'
},
methods: {
setLocalState(value) {
this.localState = Object.assign({}, value);
}
}
I used this way and it works:
store.js:
const state = {
createSuccess: false
};
mutations.js
[mutations.CREATE_SUCCESS](state, payload) {
state.createSuccess = payload;
}
actions.js
async [mutations.STORE]({ commit }, payload) {
try {
let result = await axios.post('/api/admin/users', payload);
commit(mutations.CREATE_SUCCESS, user);
} catch (err) {
console.log(err);
}
}
getters.js
isSuccess: state => {
return state.createSuccess
}
And in your component where you use state from store:
watch: {
isSuccess(value) {
if (value) {
this.$notify({
title: "Success",
message: "Create user success",
type: "success"
});
}
}
}
When user submit form, action STORE will be call, after created success, CREATE_SUCCESS mutation is committed after that. Turn createSuccess is true, and in component, watcher will see value has changed and trigger notification.
isSuccess should be match with the name you declare in getters.js
I tried literally everything to get this working.
I found that for some reason, changes to objects from $store
don't necessarily trigger a .watch
method. My workaround was to
state
to act as a flag, which does propagate changes to a Component when watched$store.mutators
to alter the complex dataset and increment the counter flag$store.state
flag. When change is detected, update locally relevant reactive changes from the $store.state
complex data set$store.state
's dataset using our $store.mutators
methodThis is implemented something like this:
Store
let store = Vuex.Store({
state: {
counter: 0,
data: { someKey: 0 }
},
mutations: {
updateSomeKey(state, value) {
update the state.data.someKey = value;
state.counter++;
}
}
});
Component
data: {
dataFromStoreDataSomeKey: null,
someLocalValue: 1
},
watch: {
'$store.state.counter': {
immediate: true,
handler() {
// update locally relevant data
this.someLocalValue = this.$store.state.data.someKey;
}
}
},
methods: {
updateSomeKeyInStore() {
this.$store.commit('updateSomeKey', someLocalValue);
}
It's convoluted but basically here we are watching for a flag to change and then updating local data to reflect important changes in an object that's stored in the $state
Vue.config.devtools = false
const store = new Vuex.Store({
state: {
voteCounter: 0,
// changes to objectData trigger a watch when keys are added,
// but not when values are modified?
votes: {
'people': 0,
'companies': 0,
'total': 0,
},
},
mutations: {
vote(state, position) {
state.votes[position]++;
state.voteCounter++;
}
},
});
app = new Vue({
el: '#app',
store: store,
data: {
votesForPeople: null,
votesForCompanies: null,
pendingVote: null,
},
computed: {
totalVotes() {
return this.votesForPeople + this.votesForCompanies
},
peoplePercent() {
if (this.totalVotes > 0) {
return 100 * this.votesForPeople / this.totalVotes
} else {
return 0
}
},
companiesPercent() {
if (this.totalVotes > 0) {
return 100 * this.votesForCompanies / this.totalVotes
} else {
return 0
}
},
},
watch: {
'$store.state.voteCounter': {
immediate: true,
handler() {
// clone relevant data locally
this.votesForPeople = this.$store.state.votes.people
this.votesForCompanies = this.$store.state.votes.companies
}
}
},
methods: {
vote(event) {
if (this.pendingVote) {
this.$store.commit('vote', this.pendingVote)
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script src="https://unpkg.com/vuex@3.5.1/dist/vuex.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
<div id="app">
<form @submit.prevent="vote($event)">
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="vote"
id="voteCorps"
value="companies"
v-model="pendingVote"
>
<label class="form-check-label" for="voteCorps">
Equal rights for companies
</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="vote"
id="votePeople"
value="people"
v-model="pendingVote"
>
<label class="form-check-label" for="votePeople">
Equal rights for people
</label>
</div>
<button
class="btn btn-primary"
:disabled="pendingVote==null"
>Vote</button>
</form>
<div
class="progress mt-2"
v-if="totalVotes > 0"
>
<div class="progress-bar"
role="progressbar"
aria-valuemin="0"
:style="'width: ' + peoplePercent + '%'"
:aria-aluenow="votesForPeople"
:aria-valuemax="totalVotes"
>People</div>
<div
class="progress-bar bg-success"
role="progressbar"
aria-valuemin="0"
:style="'width: ' + companiesPercent + '%'"
:aria-valuenow="votesForCompanies"
:aria-valuemax="totalVotes"
>Companies</div>
</div>
</div>
When you want to watch on state level, it can be done this way:
let App = new Vue({
//...
store,
watch: {
'$store.state.myState': function (newVal) {
console.log(newVal);
store.dispatch('handleMyStateChange');
}
},
//...
});
You should not use component's watchers to listen to state change. I recommend you to use getters functions and then map them inside your component.
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters({
myState: 'getMyState'
})
}
}
In your store:
const getters = {
getMyState: state => state.my_state
}
You should be able to listen to any changes made to your store by using this.myState
in your component.
https://vuex.vuejs.org/en/getters.html#the-mapgetters-helper