[javascript] vue.js 2 how to watch store values from vuex

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?

This question is related to javascript vue.js vuejs2 vuex

The answer is


if you use typescript then you can :

_x000D_
_x000D_
import { Watch } from "vue-property-decorator";_x000D_
_x000D_
.._x000D_
_x000D_
@Watch("$store.state.something")_x000D_
private watchSomething() {_x000D_
   // use this.$store.state.something for access_x000D_
   ..._x000D_
}
_x000D_
_x000D_
_x000D_


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

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

_x000D_
_x000D_
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)
      }
    }
  }
  
})
_x000D_
<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>
_x000D_
_x000D_
_x000D_


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!


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()}`)
    }
  }
})

See JSFiddle demo.


It's as simple as:

watch: {
  '$store.state.drawer': function() {
    console.log(this.$store.state.drawer)
  }
}

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


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


You could also subscribe to the store mutations:

store.subscribe((mutation, state) => {
  console.log(mutation.type)
  console.log(mutation.payload)
})

https://vuex.vuejs.org/api/#subscribe


_x000D_
_x000D_
====== store =====_x000D_
import Vue from 'vue'_x000D_
import Vuex from 'vuex'_x000D_
import axios from 'axios'_x000D_
_x000D_
Vue.use(Vuex)_x000D_
_x000D_
export default new Vuex.Store({_x000D_
  state: {_x000D_
    showRegisterLoginPage: true,_x000D_
    user: null,_x000D_
    allitem: null,_x000D_
    productShow: null,_x000D_
    userCart: null_x000D_
  },_x000D_
  mutations: {_x000D_
    SET_USERS(state, payload) {_x000D_
      state.user = payload_x000D_
    },_x000D_
    HIDE_LOGIN(state) {_x000D_
      state.showRegisterLoginPage = false_x000D_
    },_x000D_
    SHOW_LOGIN(state) {_x000D_
      state.showRegisterLoginPage = true_x000D_
    },_x000D_
    SET_ALLITEM(state, payload) {_x000D_
      state.allitem = payload_x000D_
    },_x000D_
    SET_PRODUCTSHOW(state, payload) {_x000D_
      state.productShow = payload_x000D_
    },_x000D_
    SET_USERCART(state, payload) {_x000D_
      state.userCart = payload_x000D_
    }_x000D_
  },_x000D_
  actions: {_x000D_
    getUserLogin({ commit }) {_x000D_
      axios({_x000D_
        method: 'get',_x000D_
        url: 'http://localhost:3000/users',_x000D_
        headers: {_x000D_
          token: localStorage.getItem('token')_x000D_
        }_x000D_
      })_x000D_
        .then(({ data }) => {_x000D_
          // console.log(data)_x000D_
          commit('SET_USERS', data)_x000D_
        })_x000D_
        .catch(err => {_x000D_
          console.log(err)_x000D_
        })_x000D_
    },_x000D_
    addItem({ dispatch }, payload) {_x000D_
      let formData = new FormData()_x000D_
      formData.append('name', payload.name)_x000D_
      formData.append('file', payload.file)_x000D_
      formData.append('category', payload.category)_x000D_
      formData.append('price', payload.price)_x000D_
      formData.append('stock', payload.stock)_x000D_
      formData.append('description', payload.description)_x000D_
      axios({_x000D_
        method: 'post',_x000D_
        url: 'http://localhost:3000/products',_x000D_
        data: formData,_x000D_
        headers: {_x000D_
          token: localStorage.getItem('token')_x000D_
        }_x000D_
      })_x000D_
        .then(({ data }) => {_x000D_
          // console.log('data hasbeen created ', data)_x000D_
          dispatch('getAllItem')_x000D_
        })_x000D_
        .catch(err => {_x000D_
          console.log(err)_x000D_
        })_x000D_
    },_x000D_
    getAllItem({ commit }) {_x000D_
      axios({_x000D_
        method: 'get',_x000D_
        url: 'http://localhost:3000/products'_x000D_
      })_x000D_
        .then(({ data }) => {_x000D_
          // console.log(data)_x000D_
          commit('SET_ALLITEM', data)_x000D_
        })_x000D_
        .catch(err => {_x000D_
          console.log(err)_x000D_
        })_x000D_
    },_x000D_
    addUserCart({ dispatch }, { payload, productId }) {_x000D_
      let newCart = {_x000D_
        count: payload_x000D_
      }_x000D_
      // console.log('ini dari store nya', productId)_x000D_
_x000D_
      axios({_x000D_
        method: 'post',_x000D_
        url: `http://localhost:3000/transactions/${productId}`,_x000D_
        data: newCart,_x000D_
        headers: {_x000D_
          token: localStorage.getItem('token')_x000D_
        }_x000D_
      })_x000D_
        .then(({ data }) => {_x000D_
          dispatch('getUserCart')_x000D_
          // console.log('cart hasbeen added ', data)_x000D_
        })_x000D_
        .catch(err => {_x000D_
          console.log(err)_x000D_
        })_x000D_
    },_x000D_
    getUserCart({ commit }) {_x000D_
      axios({_x000D_
        method: 'get',_x000D_
        url: 'http://localhost:3000/transactions/user',_x000D_
        headers: {_x000D_
          token: localStorage.getItem('token')_x000D_
        }_x000D_
      })_x000D_
        .then(({ data }) => {_x000D_
          // console.log(data)_x000D_
          commit('SET_USERCART', data)_x000D_
        })_x000D_
        .catch(err => {_x000D_
          console.log(err)_x000D_
        })_x000D_
    },_x000D_
    cartCheckout({ commit, dispatch }, transactionId) {_x000D_
      let count = null_x000D_
      axios({_x000D_
        method: 'post',_x000D_
        url: `http://localhost:3000/transactions/checkout/${transactionId}`,_x000D_
        headers: {_x000D_
          token: localStorage.getItem('token')_x000D_
        },_x000D_
        data: {_x000D_
          sesuatu: 'sesuatu'_x000D_
        }_x000D_
      })_x000D_
        .then(({ data }) => {_x000D_
          count = data.count_x000D_
          console.log(count, data)_x000D_
_x000D_
          dispatch('getUserCart')_x000D_
        })_x000D_
        .catch(err => {_x000D_
          console.log(err)_x000D_
        })_x000D_
    },_x000D_
    deleteTransactions({ dispatch }, transactionId) {_x000D_
      axios({_x000D_
        method: 'delete',_x000D_
        url: `http://localhost:3000/transactions/${transactionId}`,_x000D_
        headers: {_x000D_
          token: localStorage.getItem('token')_x000D_
        }_x000D_
      })_x000D_
        .then(({ data }) => {_x000D_
          console.log('success delete')_x000D_
_x000D_
          dispatch('getUserCart')_x000D_
        })_x000D_
        .catch(err => {_x000D_
          console.log(err)_x000D_
        })_x000D_
    }_x000D_
  },_x000D_
  modules: {}_x000D_
})
_x000D_
_x000D_
_x000D_


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.


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
      }
      );

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.


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');
        }
    },
    //...
});

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);
  }
 }

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


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
        }
    }
})

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.


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to vue.js

How to fix 'Unchecked runtime.lastError: The message port closed before a response was received' chrome issue? Center content vertically on Vuetify Vue.js get selected option on @change Using Environment Variables with Vue.js did you register the component correctly? For recursive components, make sure to provide the "name" option Vue 'export default' vs 'new Vue' How can I go back/route-back on vue-router? Change the default base url for axios How to reference static assets within vue javascript How to change port number in vue-cli project

Examples related to vuejs2

How can I go back/route-back on vue-router? Change the default base url for axios How to change port number in vue-cli project How to solve 'Redirect has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header'? vuetify center items into v-flex Vuejs: Event on route change Vuex - Computed property "name" was assigned to but it has no setter Vuex - passing multiple parameters to mutation How to listen to the window scroll event in a VueJS component? How to acces external json file objects in vue.js app

Examples related to vuex

Vuex - Computed property "name" was assigned to but it has no setter Vuex - passing multiple parameters to mutation vue.js 2 how to watch store values from vuex How do I format currencies in a Vue component? Returning Promises from Vuex actions