How to watch store values from vuex?
Javascriptvue.jsVuejs2VuexJavascript Problem Overview
I am using vuex
and vuejs 2
together.
I am new to vuex
, I want to watch a store
variable change.
I want to add the watch
function in my vue component
This is what I have so far:
import Vue from 'vue';
import {
MY_STATE,
} from './../../mutation-types';
export default {
[MY_STATE](state, token) {
state.my_state = token;
},
};
I want to know if there are any changes in the my_state
How do I watch store.my_state
in my vuejs component?
Javascript Solutions
Solution 1 - Javascript
Let's say, for example, that you have a basket of fruits, and each time you add or remove a fruit from the basket, you want to (1) display info about fruit count, but you also (2) want to be notified of the count of the fruits in some fancy fashion...
fruit-count-component.vue
<template>
<!-- We meet our first objective (1) by simply -->
<!-- binding to the count property. -->
<p>Fruits: {{ count }}</p>
</template>
<script>
import basket from '../resources/fruit-basket'
export default () {
computed: {
count () {
return basket.state.fruits.length
// Or return basket.getters.fruitsCount
// (depends on your design decisions).
}
},
watch: {
count (newCount, oldCount) {
// Our fancy notification (2).
console.log(`We have ${newCount} fruits now, yay!`)
}
}
}
</script>
Please note, that the name of the function in the watch
object, must match the name of the function in the computed
object. In the example above the name is count
.
New and old values of a watched property will be passed into watch callback (the count function) as parameters.
The basket store could look like this:
fruit-basket.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const basket = new Vuex.Store({
state: {
fruits: []
},
getters: {
fruitsCount (state) {
return state.fruits.length
}
}
// Obviously you would need some mutations and actions,
// but to make example cleaner I'll skip this part.
})
export default basket
You can read more in the following resources:
Solution 2 - Javascript
It's as simple as:
watch: {
'$store.state.drawer': function() {
console.log(this.$store.state.drawer)
}
}
Solution 3 - Javascript
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
Solution 4 - Javascript
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
Solution 5 - Javascript
I think the asker wants to use watch with Vuex.
this.$store.watch(
(state)=>{
return this.$store.getters.your_getter
},
(val)=>{
//something changed do something
},
{
deep:true
}
);
Solution 6 - Javascript
This is for all the people that cannot solve their problem with getters and actually really need a watcher, e.g. to talk to non-vue third party stuff (see Vue Watchers on when to use watchers).
Vue component's watchers and computed values both also work on computed values. So it's no different with vuex:
import { mapState } from 'vuex';
export default {
computed: {
...mapState(['somestate']),
someComputedLocalState() {
// is triggered whenever the store state changes
return this.somestate + ' works too';
}
},
watch: {
somestate(val, oldVal) {
// is triggered whenever the store state changes
console.log('do stuff', val, oldVal);
}
}
}
if it's only about combining local and global state, the mapState's doc also provides an example:
computed: {
...mapState({
// to access local state with `this`, a normal function must be used
countPlusLocalState (state) {
return state.count + this.localCount
}
}
})
Solution 7 - Javascript
If you simply want to watch a state property and then act within the component accordingly to the changes of that property then see the example below.
In store.js
:
export const state = () => ({
isClosed: false
})
export const mutations = {
closeWindow(state, payload) {
state.isClosed = payload
}
}
In this scenario, I am creating a boolean
state property that I am going to change in different places in the application like so:
this.$store.commit('closeWindow', true)
Now, if I need to watch that state property in some other component and then change the local property I would write the following in the mounted
hook:
mounted() {
this.$store.watch(
state => state.isClosed,
(value) => {
if (value) { this.localProperty = 'edit' }
}
)
}
Firstly, I am setting a watcher on the state property and then in the callback function I use the value
of that property to change the localProperty
.
I hope it helps!
Solution 8 - Javascript
I tried literally everything to get this working.
Theory
I found that for some reason, changes to objects from $store
don't necessarily trigger a .watch
method. My workaround was to
- Store
- Create a complex data set which should but doesn't propagate changes to a Component
- Create an incrementing counter in the
state
to act as a flag, which does propagate changes to a Component when watched - Create a method in
$store.mutators
to alter the complex dataset and increment the counter flag
- Component
- Watch for changes in the
$store.state
flag. When change is detected, update locally relevant reactive changes from the$store.state
complex data set - Make changes to the
$store.state
's dataset using our$store.mutators
method
- Watch for changes in the
Implementation
This 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);
}
Runnable demo
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/[email protected]"></script>
<script src="https://unpkg.com/[email protected]/dist/vuex.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/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>
Solution 9 - Javascript
if you use typescript then you can :
import { Watch } from "vue-property-decorator";
..
@Watch("$store.state.something")
private watchSomething() {
// use this.$store.state.something for access
...
}
Solution 10 - Javascript
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);
}
}
Solution 11 - Javascript
The best way to watch store changes is to use mapGetters
as Gabriel said.
But there is a case when you can't do it through mapGetters
e.g. you want to get something from store using parameter:
getters: {
getTodoById: (state, getters) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
in that case you can't use mapGetters
. You may try to do something like this instead:
computed: {
todoById() {
return this.$store.getters.getTodoById(this.id)
}
}
But unfortunately todoById
will be updated only if this.id
is changed
If you want you component update in such case use this.$store.watch
solution provided by Gong. Or handle your component consciously and update this.id
when you need to update todoById
.
Solution 12 - Javascript
Inside the component, create a computed function
computed:{
myState:function(){
return this.$store.state.my_state; // return the state value in `my_state`
}
}
Now the computed function name can be watched, like
watch:{
myState:function(newVal,oldVal){
// this function will trigger when ever the value of `my_state` changes
}
}
The changes made in the vuex
state my_state
will reflect in the computed function myState
and trigger the watch function.
If the state my_state
is having nested data, then the handler
option will help more
watch:{
myState:{
handler:function(newVal,oldVal){
// this function will trigger when ever the value of `my_state` changes
},
deep:true
}
}
This will watch all the nested values in the store my_state
.
Solution 13 - Javascript
Use your getter in computed then watch it and do what you need
computed:{
...mapGetters(["yourGetterName"])
},
watch: {
yourGetterName(value) {
// Do something you need
},
}
Solution 14 - Javascript
You could also subscribe to the store mutations:
store.subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.payload)
})
Solution 15 - Javascript
You can use a combination of Vuex actions, getters, computed properties and watchers to listen to changes on a Vuex state value.
HTML Code:
<div id="app" :style='style'>
<input v-model='computedColor' type="text" placeholder='Background Color'>
</div>
JavaScript Code:
'use strict'
Vue.use(Vuex)
const { mapGetters, mapActions, Store } = Vuex
new Vue({
el: '#app',
store: new Store({
state: {
color: 'red'
},
getters: {
color({color}) {
return color
}
},
mutations: {
setColor(state, payload) {
state.color = payload
}
},
actions: {
setColor({commit}, payload) {
commit('setColor', payload)
}
}
}),
methods: {
...mapGetters([
'color'
]),
...mapActions([
'setColor'
])
},
computed: {
computedColor: {
set(value) {
this.setColor(value)
},
get() {
return this.color()
}
},
style() {
return `background-color: ${this.computedColor};`
}
},
watch: {
computedColor() {
console.log(`Watcher in use @${new Date().getTime()}`)
}
}
})
Solution 16 - Javascript
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');
}
},
//...
});
Solution 17 - Javascript
Vue watch in string state
state:
$store.state.local_store.list_of_data
inside component
watch: {
'$store.state.local_store.list_of_data':{//<----------your state call in string
handler(){
console.log("value changeing in party sales entry"); //<---do your stuff here
},
deep:true
}
},
Solution 18 - Javascript
You can also use mapState in your vue component to direct getting state from store.
In your component:
computed: mapState([
'my_state'
])
Where my_state
is a variable from the store.
Solution 19 - Javascript
====== store =====
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
showRegisterLoginPage: true,
user: null,
allitem: null,
productShow: null,
userCart: null
},
mutations: {
SET_USERS(state, payload) {
state.user = payload
},
HIDE_LOGIN(state) {
state.showRegisterLoginPage = false
},
SHOW_LOGIN(state) {
state.showRegisterLoginPage = true
},
SET_ALLITEM(state, payload) {
state.allitem = payload
},
SET_PRODUCTSHOW(state, payload) {
state.productShow = payload
},
SET_USERCART(state, payload) {
state.userCart = payload
}
},
actions: {
getUserLogin({ commit }) {
axios({
method: 'get',
url: 'http://localhost:3000/users',
headers: {
token: localStorage.getItem('token')
}
})
.then(({ data }) => {
// console.log(data)
commit('SET_USERS', data)
})
.catch(err => {
console.log(err)
})
},
addItem({ dispatch }, payload) {
let formData = new FormData()
formData.append('name', payload.name)
formData.append('file', payload.file)
formData.append('category', payload.category)
formData.append('price', payload.price)
formData.append('stock', payload.stock)
formData.append('description', payload.description)
axios({
method: 'post',
url: 'http://localhost:3000/products',
data: formData,
headers: {
token: localStorage.getItem('token')
}
})
.then(({ data }) => {
// console.log('data hasbeen created ', data)
dispatch('getAllItem')
})
.catch(err => {
console.log(err)
})
},
getAllItem({ commit }) {
axios({
method: 'get',
url: 'http://localhost:3000/products'
})
.then(({ data }) => {
// console.log(data)
commit('SET_ALLITEM', data)
})
.catch(err => {
console.log(err)
})
},
addUserCart({ dispatch }, { payload, productId }) {
let newCart = {
count: payload
}
// console.log('ini dari store nya', productId)
axios({
method: 'post',
url: `http://localhost:3000/transactions/${productId}`,
data: newCart,
headers: {
token: localStorage.getItem('token')
}
})
.then(({ data }) => {
dispatch('getUserCart')
// console.log('cart hasbeen added ', data)
})
.catch(err => {
console.log(err)
})
},
getUserCart({ commit }) {
axios({
method: 'get',
url: 'http://localhost:3000/transactions/user',
headers: {
token: localStorage.getItem('token')
}
})
.then(({ data }) => {
// console.log(data)
commit('SET_USERCART', data)
})
.catch(err => {
console.log(err)
})
},
cartCheckout({ commit, dispatch }, transactionId) {
let count = null
axios({
method: 'post',
url: `http://localhost:3000/transactions/checkout/${transactionId}`,
headers: {
token: localStorage.getItem('token')
},
data: {
sesuatu: 'sesuatu'
}
})
.then(({ data }) => {
count = data.count
console.log(count, data)
dispatch('getUserCart')
})
.catch(err => {
console.log(err)
})
},
deleteTransactions({ dispatch }, transactionId) {
axios({
method: 'delete',
url: `http://localhost:3000/transactions/${transactionId}`,
headers: {
token: localStorage.getItem('token')
}
})
.then(({ data }) => {
console.log('success delete')
dispatch('getUserCart')
})
.catch(err => {
console.log(err)
})
}
},
modules: {}
})
Solution 20 - Javascript
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
Solution 21 - Javascript
You can also watch it safely with debouncedWatch (vue use function)
debouncedWatch(
lines,
() => {
console.log('changed');
},
500,
);