Vuex store.watch behavior - javascript

I'm a Vuejs noob and i'm a bit stuck with Vuex store's watch function.
Here's my code for the store:
const storeconf: StoreOptions<any> = {
state: {
string: 'teststring'
},
actions:{
changeString({commit}):any{
commit('stringChange');
}
},
mutations:{
stringChange(state){
state.string=Math.random()+' root';
}
},
modules: {
mod:{
namespaced:true,
state:{
string:'modulestring'
},
actions:{
changeString({commit}):any{
commit('stringChange');
}
},
mutations:{
stringChange(state){
state.string=Math.random()+' module';
}
},
}
}
..and the app:
new Vue({
el: '#app',
store: store,
template:'<button #click="doActions">click</button>',
mounted(){
this.$store.watch(
state =>[state.string, state.mod.string],
watched => console.log(watched[0]+' -- '+watched[1]);
);
},
methods:{
doActions:function(){
this.$store.dispatch('mod/changeString')
this.$store.dispatch('changeString');
}
}
The point is that when i trigger the action on the root store the watcher works perfectly, but when i trigger the namespaced module one ( mod/changeString ) the watcher does nothing at all.
What's weird to me is that the namespaced action changes properly the store value, it just won't trigger the watcher..
What am I doing wrong?

thanksd it does indeed.
The real project I extracted this example from is in typescript, following this tutorial, and it seems the problem is that module property was set as optional and not initialized. It still logged the mutation and modified the state but did not trigger any watch. Kinda weird.

Related

Websockets in Vue/Vuex (how to receive emissions from server)

So until now I just socket.io-client to do communication to a WebSocket in my Vue component.
Now I am adding Vuex to the project and declared a Websocket like this
Vue.use(new VueSocketIO({
debug: true,
connection: 'http://192.168.0.38:5000',
}));
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
1) Should i have stuff like emitting some messages in the component themselves now or in the store?
2) Before I introduced the changes I could do something like this:
socket.on('connect', function () {
console.error('connected to webSocket');
socket.emit('my event', { data: 'I\'m connected!' });
});
socket.on('my response', function(data){
console.log('got response');
console.log(data.data);
});
When sending the "my event", the flask server would respond with "my response". Now I am trying the same thing from a component after the changes like this.
this.$socket.emit('my_event', { data: 'I\'m connected!' });
console.error('send to websocket ');
this.$options.sockets.my_event = (data) => {
console.error('received answer ');
console.error(data);
};
The my_event reaches my flask server however I don't get the response receiving to work. What am I doing wrong?
Also because I was asking about whether I should put this in the component or the store, I found stuff like this for the store:
SOCKET_MESSAGECHANNEL(state, message) {
state.socketMessage = message
}
The explanation was "So, for example, if your channel is called messageChannel, the corresponding Vuex mutation would be SOCKET_MESSAGECHANNEL" and it is from this site https://alligator.io/vuejs/vue-socketio/.
I think I don't really get what a channel is at this point. Is the my_response I emit from the flask server also a channel?
Thanks for your help in advance!
EDIT: So now I am trying to listen and emit to a websocket from my store. For this I tried the following: In main.js I have this part:
Vue.use(new VueSocketIO({
debug: true,
connection: SocketIO('http://192.168.0.38:5000'),
vuex: {
store,
actionPrefix: 'SOCKET_',
mutationPrefix: 'SOCKET_',
},
}));
Then in my store.js I have the following:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0,
title: 'title from vuex store',
isConnected: false,
},
mutations: {
increment(state) {
state.count += 1;
},
emitSth(state) {
this.sockets.emit('my_event', { data: 'I\'m connected!' });
console.log(state.count);
},
SOCKET_my_response(state) {
state.isConnected = true;
alert(state.isConnected);
},
SOCKET_connect(state) {
state.isConnected = true;
alert(state.isConnected);
},
},
});
And in my component I have this script:
export default {
name: 'ControlCenter',
data() {
return {
devices: [{ ip: 'yet unknown' }], // placeholder so line 12 does not throw error before actual device info fetched
thisDeviceIndex: 0,
currentLayoutIndex: 0,
layouts: [],
};
},
computed: mapState([
'title',
'count',
]),
components: {
DNDAssign,
FirstPage,
},
methods: {
// mapMutation helper let's us use mutation from store via this instead of this.$store
...mapMutations([
'increment',
'emitSth',
]),
incrementMutation() {
this.increment();
},
emitEvent() {
this.emitSth();
},
// some other stuff here
},
created() {
// inital fetching of layouts
console.log('fetching layouts from backend');
this.getAllLayouts();
console.log(this.$socket);
},
};
I also have a button for the triggering of the emit which is
<b-button
type="button"
variant="success"
v-on:click="emitEvent()"
>
emit event
</b-button>
The connected in the store gets triggered, however I get the following errors for the emitting:
"TypeError: Cannot read property 'emit' of undefined"
"Cannot read property 'emit' of undefined"
Also I am not sure about the naming in the mutations. If I have this mutationPrefix, shouldn't it be enough to just use connect instead of SOCKET_connect?
First of all, if you are using Vue-Socket.io version 3.0.5>, uninstall it and install version 3.0.5
npm uninstall vue-socket.io
npm install vue-socket.io#3.0.5
then lock the version in packege.json: "vue-socket.io": "3.0.5", latest update seems to breaks the library, read more here
Now to receive events from socket.io server, you can do:
this.sockets.subscribe("my response", (data) => {
console.log(data);
});
or if want put listener on component level, you need to add sockets object on the component export, for example:
export default {
...
sockets: {
"my response": function (data) {
console.log(data);
}
}
...
}
Since you are not using Vuex Integration on the VueSocketIO, you dont need to put additional function in store mutation. If you want to use Vuex integration on VueSocketIO, you need to add vuex object when declaring the VueSocketIO class.
Here's the basic example for main.js
// Set Vue to use Vuex
Vue.use(Vuex);
// Create store
const store = new Vuex.Store({
state: {
someData: null
},
getters: {},
actions: {
"SOCKET_my response"(context, data) {
// Received `my response`, do something with the data, in this case we are going to call mutation "setData"
context.commit("setData", data);
}
}
mutations: {
["setData"](state, data) {
state.someData = data; // Set it to state
}
}
});
// Set Vue to use VueSocketIO with Vuex integration
Vue.use(new VueSocketIO({
debug: true,
connection: 'http://192.168.0.38:5000',
vuex: {
store,
actionPrefix: "SOCKET_"
}
}));
new Vue({
router,
store
render: h => h(App)
}).$mount("#app");
If you need example on Vuex Integration, you can check my example app that uses Vue and Vue-Socket.io with Vuex integration.te

When <router-view/> changed,why can the old component of router still '$on' and handle the event?

emmmmm...let me explain the situation I meet.
I have a parent component with two children that both litsen the same event and do the same thing.(codes below):
mounted() {
EventBus.$on('edit', (data) => {
console.log('service called')
this.showRightSide(data)
})
},
showRightSide(data) {
console.log(data)
// display right-side operator edit page.
this.$store.commit({
type: 'setShownState',
shown: true
})
// giving operator name & operator type
this.$store.commit({
type: 'setOptName',
optName: data.name
})
this.$store.commit({
type: 'setOptType',
optType: data.type
})
},
with the vue router below
{
path: '/main',
name: 'Main',
component: Main,
children: [
{ path: 'service', name: 'Service', component: ServiceContent },
{ path: 'model', name: 'Model', component: ModelContent }
]
},
There should be three commits during each 'edit' event, isn't it?
In fact. Firstly it has 3 commits.
But when I change from '/main/service' to '/main/model', it made 6 commits during each 'edit' event(the old ServiceContent component still made 3 commits and the new ModelContent component offers 3 commits).
when I back to '/main/service', 9 commits!!!
devtool:
It seems that when router-view changed, the component of old view can still listen the event, how can I fix it?
(EventBus is just a global vue instance used as a bus)
When you call $on(), Vue registers your callback function internally as an observer. This means your function lives on, even after the component is unmounted.
What you should do is use $off when your component is unmounted.
For example
methods: {
showRights (data) {
// etc
}
},
mounted () {
EventBus.$on('edit', this.showRights)
},
beforeDestroy () {
EventBus.$off('edit', this.showRights)
}
I would start with manually cleaning up listeners in your beforeUnmount function. Because of the way that JS works with garbage collection, I would be surprised if vue is smart enough to clean up externally reference stuff like this.
methods: {
handleEventBusEdit(data) {
console.log('service called')
this.showRightSide(data)
}
},
mounted() {
EventBus.$on('edit', this.handleEventBusEdit)
},
beforeDestroy() {
EventBus.$off('edit', this.handleEventBusEdit)
}

Vue.js $watch Not Working

First, I have a Vue JS file --
export var myVue = new Vue({
el: '#myApp',
data: {
myCoordinates: new Set() // simple integers
},
methods: {
addCoordinates (c) {
this.myCoordinates.add(c);
}
}
}
Then I have a another JS file that imports the Vue and does some plotting --
import { myVue } from './my_vue.js';
myVue.addCoordinates(42);
myVue.$watch('myCoordinates', function(newVal, oldVal) {
console.log(newVal);
// plot the new coordinates
}, { deep: true });
The problem is the myVue.$watch does not fire -- I can see myCoordinates updated properly in Vue dev console, but myVue.$watch just doesn't get trigggered. And I can't test watch as Vue's native as I can't move the plotting part into myVue due to various restrictions.
The question is: What could have gone wrong in my case?
Edit: BTW, I'm using Webpack 4 to combine both JS files, would that have any side-effect?
It's very likely that the watcher has not been registered when you update your coordinates as show in your code (it's registered after you call add function). Similar to any kind of event listeners, you need to register the watcher / handler before the event happens for it to trigger.
With Vue, you can always register the watcher functions when you create a new Vue instance, for example:
export var myVue = new Vue({
el: '#myApp',
data: {
myCoordinates: new Set() // simple integers
},
methods: {
addCoordinates (c) {
this.myCoordinates.add(c);
}
},
watch: {
myCoordinates: function(new, old) {
console.log(new, old);
}
}
}
It turns out that Vue2 watch does not support Set. See answers to my next question Vue2 watch Set Not Working

vue is not defined on the instance but referenced during render

I'm trying to build a simple app in vue and I'm getting an error. My onScroll function behaves as expected, but my sayHello function returns an error when I click my button component
Property or method "sayHello" is not defined on the instance but
referenced during render. Make sure to declare reactive data
properties in the data option. (found in component )
Vue.component('test-item', {
template: '<div><button v-on:click="sayHello()">Hello</button></div>'
});
var app = new Vue({
el: '#app',
data: {
header: {
brightness: 100
}
},
methods: {
sayHello: function() {
console.log('Hello');
},
onScroll: function () {
this.header.brightness = (100 - this.$el.scrollTop / 8);
}
}
});
I feel like the answer is really obvious but I've tried searching and haven't come up with anything. Any help would be appreciated.
Thanks.
But for a few specific circumstances (mainly props) each component is completely isolated from each other. Separate data, variables, functions, etc. This includes their methods.
Thus, test-item has no sayHello method.
You can get rid of the warning by using .mount('#app') after the Vue instance rather than the el attribute.
Check the snippet below;
var app = new Vue({
data: {
header: {
brightness: 100
}
},
methods: {
sayHello: function() {
console.log('Hello');
},
onScroll: function () {
this.header.brightness = (100 - this.$el.scrollTop / 8);
}
}
}).mount('#app');
Please note; the following might not be necessary but did it along the way trying to solve the same issue: Laravel Elixir Vue 2 project.

How can I access a parent method from child component in Vue.js?

I am trying to call a parent/root level method on a child component in Vue.js, but I keep getting a message saying TypeError: this.addStatusClass is not a function.
Vue.component('spmodal', {
props: ['addStatusClass'],
created: function() {
this.getEnvironments();
},
methods: {
getEnvironments: function() {
this.addStatusClass('test');
}
}
});
new Vue({
el: '#app',
methods: {
addStatusClass(data) {
console.log(data);
}
}
});
Here is a full JSBIN example: http://jsbin.com/tomorozonu/edit?js,console,output
If I call this.$parent.addStatusClass('test'); it works fine, but based on the Vue.js documentation, this is bad practice and I should be using props which is not working.
specifying the prop does nothing on its own, you have to actually pass something to it from the parent - in this case, the function.
<spmodal :add-status-class="addStatusClass"></spmodal>

Categories

Resources