How to stop useAsyncState from running immediately? - vue.js

I am trying to use the useAsyncState() from the VueUse library.
I am using the { immediate: false } option flag because I do NOT want it to run immediately.
But the code always gets run immediately, and I don't know why.
Here is my code:
import { useAsyncState } from '#vueuse/core';
const firstNameSubmit = useAsyncState(
new Promise((resolve) => {
setTimeout(() => {
console.log('Bob');
resolve();
}, 2000);
}),
undefined,
{ immediate: false }
);
const handleFirstNameSubmit = () => {
firstNameSubmit.execute();
};
And here is a reproduction: https://stackblitz.com/edit/vitejs-vite-ylzoyt/?file=src%2FApp.vue
I also tried doing the opposite and using { immediate: true }, along with excluding the options object altogether, but none of those solutions work.
The promise inside useAsyncState always runs immediately.

Once a promise is created, it cannot be postponed. In order to avoid this, it should be wrapped with a function that can be explicitly called.
It should be:
const firstNameSubmit = useAsyncState(
() => ...,
undefined,
{ immediate: false }
);
...
firstNameSubmit.execute();

Related

Nuxt watch does not redirect

I have a Nuxt application with profile page. This page has a watcher which checks store.state.auth.isAuthenticated value. If it is false watcher should redirect to login page. The weird is that although the condition is evaluated right it does not redirect to login.
watch: {
'$store.state.auth.isAuthenticated': {
imediate: true,
deep: false,
handler(newVal) {
if( !newVal ) this.$router.push({'name': 'login'});
}
},
},
As I wrote above, condition is evaluated right but it does not trigger $router.push(). I dont understand it. What is wrong with that code?
EDIT: It creates the endless loop in auth.js middleware.
import { createNavigationGuard } from "~/plugins/navigation-guard.js";
export default function (context) {
if (process.client) {
const watchedStores = [];
const unwatchStore = (unwatch) => {
if (typeof unwatch === "function") {
unwatch();
}
};
// TODO: Find out whether the watchers persist after each route
// Unwatch previous route - this could be necessary for performance
console.log('watchedStores');
console.log(watchedStores);
unwatchStore(watchedStores.pop());
const unwatch = context.store.watch(
(state) => {
return state.auth.isAuthenticated;
},
(isAuthenticated) => {
createNavigationGuard(context, isAuthenticated);
},
{
immediate: true,
}
);
// it's not necessary to reassign unwatched variable to undefined with array
watchedStores.push(unwatch);
}
if (process.server) {
createNavigationGuard(
context,
context.store.state.auth.isAuthenticated
);
}
}
have you tried to make a method for the redirect and just call that method in your watch handler?
so instead of this.$router.push do this.redirectUser()
and in the method 'redirectUser()' do:
this.$router.push({'name': 'login'})

Vuejs created and mounted doesn't work properly even if at the same level than methods

I'm experiencing a strange behaviour with created() and mounted() in Vue.js. I need to set 2 lists in created() - so it means those 2 lists will help me to create a third list which is a merge.
Here is the code :
// return data
created () {
this.retrieveSellOffers();
this.getAllProducts();
},
mounted () {
this.mergeSellOffersProducts();
},
methods: {
retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
SellOfferServices.getAllBySellerId(this.sellerId)
.then((response) => {
this.sellOffers = response.data;
console.log("this.sellOffers");
console.log(this.sellOffers);
})
.catch((e) => {
console.log(e);
});
},
getAllProducts() {
ProductServices.getAll()
.then((response) => {
this.products = response.data;
console.log("this.products");
console.log(this.products);
})
.catch((e) => {
console.log(e);
});
},
mergeSellOffersProducts () {
console.log(this.products) // print empty array
console.log(this.sellOffers) // print empty array
for (var i = 0; i < this.sellOffers.length; i++) {
if (this.sellOffers[i].productId === this.products[i]._id) {
this.arr3.push({id: this.sellOffers[i]._id, price: this.sellOffers[i].price, description: this.products[i].description});
}
}
this.arr3 = this.sellOffers;
},
}
//end of code
So my problem is when I enter in mergeSellOffersProducts(), my 2 lists are empty arrays :/
EDIT :
This way worked for me :
async mounted() {
await this.retrieveSellOffers();
await this.getAllProducts();
this.mergeSellOffersProducts();
},
methods: {
async retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
this.sellOffers = (await axios.get('link/api/selloffer/seller/', { params: { sellerId: this.sellerId } })).data;
},
async getAllProducts() {
this.products = (await axios.get('link/api/product')).data;
},
}
I think the reason is: Vue does not wait for the promises to resolve before continuing with the component lifecycle.
Your functions retrieveSellOffers() and getAllProducts() contain Promise so maybe you have to await them in the created() hook:
async created: {
await this.retrieveSellOffers();
await this.getAllProducts();
}
So I tried to async my 2 methods :
async retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
this.sellOffers = (await axios.get('linkhidden/api/selloffer/', { params: { sellerId: '615b1575fde0190ad80c3410' } })).data;
console.log("this.sellOffers")
console.log(this.sellOffers)
},
async getAllProducts() {
this.products = (await axios.get('linkhidden/api/product')).data;
console.log("this.products")
console.log(this.products)
},
mergeSellOffersProducts () {
console.log("here")
console.log(this.sellOffers)
console.log(this.products)
this.arr3 = this.sellOffers;
},
My data are well retrieved, but yet when I enter in created, the two lists are empty...
You are calling a bunch of asynchronous methods and don't properly wait for them to finish, that's why your data is not set in mounted. Since Vue does not await its lifecycle hooks, you have to deal with the synchronization yourself.
One Vue-ish way to fix it be to replace your method mergeSellOffersProducts with a computed prop (eg mergedSellOffersProducts). Instead of generating arr3 it would simply return the merged array. It will be automatically updated when products or sellOffers is changed. You would simply use mergedSellOffersProducts in your template, instead of your current arr3.
If you only want to update the merged list when both API calls have completed, you can either manually sync them with Promise.all, or you could handle this case in the computed prop and return [] if either of the arrays is not set yet.
When you're trying to merge the 2 lists, they aren't filled up yet. You need to await the calls.
async created () {
await this.retrieveSellOffers();
await this.getAllProducts();
},
async mounted () {
await this.mergeSellOffersProducts();
},

nested property inside watch event

I know my question maybe a duplicate but i cant find the solultion of my problem anywhere
I have an object with some key/values and i want to watch for the changes in all properties,keeping in mind all properties must have separate events here my object
data: () => ({
filters: {
All: false,
AllUSA: false,
AllSouthAmerica: false,
AllAsia: false,
AllEurope: false
}
}),
watch
watch: {
"filters.All": function(val) {
if (val) {
this.TrafficSource = Object.assign(...Object.keys(this.TrafficSource).map(k => ({
[k]: true
})));
this.filters = Object.assign(...Object.keys(this.filters).map(k => ({
[k]: true
})));
} else {
this.TrafficSource = Object.assign(...Object.keys(this.TrafficSource).map(k => ({
[k]: false
})));
this.filters = Object.assign(...Object.keys(this.filters).map(k => ({
[k]: false
})));
}
},
"filters.AllUSA": function(val) {
alert("both event being called")
this.TrafficSource = Object.assign(...Object.keys(this.TrafficSource).map(k => ({
[k]: false
})));
this.filters = Object.assign(...Object.keys(this.filters).map(k => ({
[k]: false
})));
if (val) {
this.TrafficSource.Virginia = true;
this.TrafficSource.California = true;
this.TrafficSource.Oregon = true;
} else {
this.TrafficSource.Virginia = false;
this.TrafficSource.California = false;
this.TrafficSource.Oregon = false;
}
},
deep: true
}
the problem right now is both the watch events are called event though filters.All was invoked, what am i doing wrong?
you can use a watcher with options on filters like this:
watch: {
filters: {
immediate: true,
deep: true,
handler(newValue, oldValue) {
// you can write code to tell you which property is changed since you have
access to the old and new value here
}
},
};
this kind of watcher runs immediately when your component loads and then since it has deep: true it will trigger if any property in the object its watching gets changed.
now for the code to track what actually changed you can write something like this:
// all of this code should be done in the watcher's handler
const newVals = Object.values(newValue);
const oldVals = Object.values(oldValue);
const changedVal = [];
newVals.forEach((x, i) => {
if (x !== oldVals[i]) {
const result = Object.entries(newValue).filter(y => y[1] === x).flat();
changedVal.push(result[0]);
}
});
now you have all the changed keys in the changedVal array in the handler and you can continue with your logic in the handler
Edit: also i don't think changedVal should be an array, you can set it to the changed key and write a switch...case based on that

mocking setInterval in created hook (vue.js)

I am trying to mock a setInterval inside my created hook but no matter what I try
the function is never called. What I have done so far is using jest.useFakeTimers and inside
each test I would use jest.advanceTimersByTime(8000) to check if my api is being called.
I would appreciate any opinions/help. thanks
my vue file
created() {
setInterval(() => this.checkStatus(), 8000)
},
methods: {
async checkStatus() {
let activated = false
if (!this.isLoading) {
this.isLoading = true
let res = await this.$UserApi.getUserActivateStatus(this.accountId)
this.isLoading = false
if (res.success) {
activated = res.activated
}
if (activated) {
console.log("activated")
} else {
console.log("error")
}
}
}
}
my test file
import { shallowMount, config } from "#vue/test-utils"
import Step4 from "../../../login/smart_station/step4"
describe("Step4", () => {
let wrapper
const $route = {
query: {
account_id: "99"
}
}
const mockGetUserActivateStatus = jest.fn(() =>
Promise.resolve({ success: true, activated: true })
)
beforeEach(() => {
wrapper = shallowMount(Step4, {
mocks: {
$UserApi: {
getUserActivateStatus: mockGetUserActivateStatus
}
}
})
jest.useFakeTimers()
})
it("activates status every 8secs", async () => {
jest.advanceTimersByTime(9000)
expect(mockGetUserActivateStatus).toHaveBeenCalled()
})
})
Jest's Timer Mocks replace the native timer functions like setInterval with their own versions that can be controlled.
Your problem is that you are telling Jest to replace these functions after your component is created and mounted. Since you're using setInterval within your component's created hook, this will still be using the real version.
Move the jest.useFakeTimers() to the top of the beforeEach setup function
beforeEach(() => {
jest.useFakeTimers()
wrapper = shallowMount(Step4, {
mocks: {
$UserApi: {
getUserActivateStatus: mockGetUserActivateStatus
}
}
})
})

Why my vue js data member is not getting updated?

Data part
data () {
return {
containsAd: true
}
},
Method that manipulates the data member containsAd
updated () {
let _this = this
window.googletag.pubads().addEventListener('slotRenderEnded', function (event) {
if (event.slot.getSlotElementId() === 'div-gpt-ad-nativead1') {
_this.containsAd = !event.isEmpty // this is false
console.log('Ad Exists? ', _this.containsAd)
}
})
},
Just to check if the value has changed or not.
check () {
let _this = this
setTimeout(function () {
console.log('Current Value', _this.containsAd)
}, 5000)
}
Resulting Output
I think doing the event listener in the mounted hook will sort your issue.
data() {
return {
containsAd: true
};
},
mounted() {
window.googletag.pubads().addEventListener('slotRenderEnded', event => {
if (event.slot.getSlotElementId() === 'div-gpt-ad-nativead1') {
this.containsAd = ! event.isEmpty // this is false
console.log('Ad Exists?', this.containsAd);
}
});
}
Also using es6 shorthand function will avoid you having to set _this.