Vuex store unwatch - vuex

store.watch function returns the unwatch function as documentation described.
I need to watch my state for once. So, can I call unwatch function inside the watch method second parameter callback function or should I do it somewhere else ?
var unwatch = this.$store.watch(
state => state.foo,
() => {
someMethod();
unwatch(); // Is this work ?
}
)
PS: I could not understand watcher state, with logging it to console.

So, can I call unwatch function inside the watch method second parameter callback function or should I do it somewhere else ?
Answer:
Calling it inside the watch callback will work!
Here is a codesandbox example
In the example I have a count which is stored in vuex store and a localCount which is stored in the component's state.
Whenever the count in the store changes, I call a method to update the localCount. Successfully, the localCount updated only once:
The template:
<div id="app">
Local Count {{ localCount }}
<hr>
store count {{ $store.state.count }}
<hr>
<button #click="increase">Increase</button>
</div>
The script:
data: () => ({
localCount: 0
}),
created() {
const unwatch = this.$store.watch(
state => state.count,
() => {
this.myMethod();
unwatch();
}
);
},
methods: {
myMethod() {
this.localCount++;
},
increase() {
this.$store.commit("increase");
}
}
When and where to call the unwatch function it depends on you needs. You can even store the unwatch function to the vue instance and when you don't want to watch anymore you can do this.unwatch():
this.unwatch = this.$store.watch(
state => state.foo,
() => {
someMethod();
}
)

Related

Vue 3 ref changes the DOM but reactive doesn't

The following code works and I can see the output as intended when use ref, but when using reactive, I see no changes in the DOM. If I console.log transaction, the data is there in both cases. Once transaction as a variable changes, should the changes not be reflected on the DOM in both cases?
I'm still trying to wrap my head around Vue 3's composition API and when to use ref and reactive. My understanding was that when dealing with objects, use reactive and use ref for primitive types.
Using ref it works:
<template>
{{ transaction }}
</template>
<script setup>
import { ref } from 'vue'
let transaction = ref({})
const getPayByLinkTransaction = () => {
axios({
method: "get",
url: "pay-by-link",
params: {
merchantUuid: import.meta.env.VITE_MERCHANT_UUID,
uuid: route.params.uuid,
},
})
.then((res) => {
transaction.value = res.data
})
.catch((e) => {
console.log(e)
})
}
getPayByLinkTransaction()
</script>
Using reactive it doesn't work:
<template>
{{ transaction }}
</template>
<script setup>
import { reactive } from 'vue'
let transaction = reactive({})
const getPayByLinkTransaction = () => {
axios({
method: "get",
url: "pay-by-link",
params: {
merchantUuid: import.meta.env.VITE_MERCHANT_UUID,
uuid: route.params.uuid,
},
})
.then((res) => {
transaction = { ...res.data }
})
.catch((e) => {
console.log(e)
})
}
getPayByLinkTransaction()
</script>
Oh, when you do transaction = { ...res.data } on the reactive object, you override it, like you would with any other variable reference.
What does work is assigning to the reactive object:
Object.assign(transaction, res.data)
Internally, the object is a Proxy which uses abstract getters and setters to trigger change events and map to the associated values. The setter can handle adding new properties.
A ref() on the other hand is not a Proxy, but it does the same thing with its .value getter and setter.
From what I understand, the idea of reactive() is not to make any individual object reactive, but rather to collect all your refs in one single reactive object (somewhat similar to the props object), while ref() is used for individual variables. In your case, that would mean to declare it as:
const refs = reactive({transaction: {}})
refs.transaction = { ...res.data }
The general recommendation seems to be to pick one and stick with it, and most people seem to prefer ref(). Ultimately it comes down to if you prefer the annoyance of having to write transaction.value in your script or always writing refs.transaction everywhere.
With transaction = { ...res.data } the variable transaction gets replaced with a new Object and loses reactivity.
You can omit it by changing the data sub-property directly or by using ref() instead of reactivity()
This works:
let transaction = ref({})
transaction.data = res.data;
Check the Reactivity in Depth and this great article on Medium Ref() vs Reactive() in Vue 3 to understand the details.
Playground
const { createApp, ref, reactive } = Vue;
const App = {
setup() {
const transaction1 = ref({});
let transaction2 = reactive({ data: {} });
const res = { data: { test: 'My Test Data'} };
const replace1 = () => {
transaction1.value = res.data;
}
const replace2 = () => {
transaction2.data = res.data;
}
const replace3 = () => {
transaction2.data = {};
}
return {transaction1, transaction2, replace1, replace2, replace3 }
}
}
const app = Vue.createApp(App);
app.mount('#app');
#app { line-height: 2; }
[v-cloak] { display: none; }
<div id="app">
transaction1: {{ transaction1 }}
<button type="button" #click="replace1()">1</button>
<br/>
transaction2: {{ transaction2 }}
<button type="button" #click="replace2()">2</button>
<button type="button" #click="replace3()">3</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
Since reactive transaction is an object try to use Object.assign method as follows :
Object.assign(transaction, res.data)

Problem with rendering data and heavy data loading

The first problem is that when getDetails(‘multiple’, ‘2’) function is called inside HTML, it takes some time until de data is displayed from v-for.
The second problem is that when I call the console.log(userDetails) from inside of anotherFunction() I got the undefined answer. It doesn’t wait for the this.getDetails(‘multiple’, ‘1’) to execute completely.
How can I improve the time for rendering, or should I use another way to display de data?
How can I make the second function to wait until the first function is complete?
VUE version: 2.7.10
<div id="app">
<p v-for="item in userDetails">item is displayed</p> //
<button #click="anotherFunction()">Click Me!</button>
</div>
<script>
export default {
name: 'App',
data: {
userDetails: []
}
}
// axios function
getDetails(actionType, idUser) {
axios.post("https://example.com/link", {
Username: username
}).then(response => {
const result = response.data;
// push data into variable
this.userDetails.push(result[0])
}).catch(error => {
this.showError('Error', 4000);
console.error('Error:' + error);
});
// another function from where I want to call the axios function
anotherFunction() {
this.getDetails('multiple', '1')
// call the userDetails into another function will output "undefined"
console.log(this.userDetails);
}

Undefined property error before store gets populated by API call

In my child component, I'm trying to access a nested property (called background_color) of a store state object (called template) :
<template>
<div
class="btn"
:style="{ 'background-color': backgroundColor }"
>
content
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
backgroundColor: state => state.template.button_branding.secondary_button.background_color
})
}
}
</script>
The store is populated from the parent view component with a store action call:
created () {
this.initializeStore()
},
In my store, this action is defined thus:
export const actions = {
initializeStore ({ state, commit }, data) {
this.$axios.get('path.to.api.endpoint')
.then((res) => {
commit('setConfig', res.data) // populates store
}).catch((error) => {
console.log(error)
})
}
}
I'm getting this error:
Cannot read property 'secondary_button' of undefined
Yet I see my nested property populated in the state via Vue DevTools.
How can I avoid this error?
You could use optional chaining combined with nullish coalescing to add a default value like this:
backgroundColor: state => state.template?.button_branding?.secondary_button?.background_color ?? 'default_value'
This should protect against errors while the API is being called.
Also, it might be neater to move your logic into a getter inside the store, and set the default value there:
getters: {
backgroundColor: state => {
return state.template?.button_branding?.secondary_button?.background_color ?? 'default_value'`;
}
}
That way the component can use mapGetters and it doesn't need to care whether or not the default value is being used.

Is there any way to check the status of props when passing data from parent to child component

I have been troubled by a question for a long time. Now I am using Vue.js to develop a web project. What I want to do is to pass data from parent to child component. However, the child component's main program would run only after the props data was received, due to the async data transmission mechanism. So I would like to know whether these are some ways to check the status of props data in the child component. Therefore I can make sure the subsequent task would run after the data was passed.
For example, a feasible solution is axios.requset({..}).then(res => {..}).
You can use the watchers in your child component. Consider the following parent component:
Vue.component('parent-comp', {
props: ['myProp'],
template: `
<div>
<child-comp my-prop={someAsyncProp} />
</div>
`,
data() {
return {
// Declare async value
someAsyncProp: null
};
},
mounted() {
// Some async computation
axios
.requset({ url: '/get-data' })
.then(res => {
// Set value asynchronously
this.someAsyncProp = res;
});
}
});
Your child component would use watchers to check if data is available:
Vue.component('child-comp', {
props: ['myProp'],
template: '<div></div>',
watch: {
// Watch the property myProp
myProp(newValue, oldValue) {
if (newValue !== null) {
// Do something with the props that are set asynchronously by parent
}
}
}
})

Can't acces prop inside method

I'm creating a single file component in Vue which takes an object as a prop called data. I want to access this prop inside a custom function that I'm creating. Here's my code
<script>
import { db } from "../main";
export default {
name: "UserDetail",
props: ["data"],
methods: {
verifyUserProfile: () => {
console.log(this.data);
db.collection("users")
.doc(this.data.uid)
.update({
idProofVerified: true
})
}
}
};
</script>
When I use the data prop in template it works just fine but in the function its giving me Cannot read property 'data' of undefined error. What am I doing wrong?
Update: I'm using this component inside View like so:
<div>
<UserDetail :data="users[selected]" />
</div>
Where users is an array of users objects.
You're using () => {} so this points to the global context. Replace it with verifyUserProfile: function () { ... }.
You are most likely calling the method before the props have arrived at the component, typically the props come in only after the lifecycle methods and therefore if you call a function which tries to access them there it will be undefined.
A solution to this would be to place a watcher on the props and then trigger the function once the props have received a value.
Like so:
watch: {
data: {
immediate: true,
handler(val){
if (val){
this.verifyUserProfile()
}
}
}
}