Loading effect does'nt work while calling a function - vue.js

I wrote the code to implement a loading effect. However, when I click the button, the loading effect doesn't work. But, if I remove the function, the loading effect works. It seems the function prevent loading effect while it is running. What is the problem?
<template>
<div v-loading="loading">
<center><el-button #click="updateData">Click</el-button></center>
</div>
</template>
data() {
return {
loading: false
}
},
methods: {
updateData() {
this.loading = true
uploadData().then(response => {
const data = response.data
if (data.code === 200) {
this.$message.warning({ message: 'OK', duration: 5000 })
} else {
this.$message.error({ message: data.msg, duration: 5000 })
}
})
this.loading = false
},
....
}

You are calling this.loading = false right after your uploadData() method which is running async. Place the line inside your then() method as follows:
<template>
<div v-loading="loading">
<center><el-button #click="updateData">Click</el-button></center>
</div>
</template>
data() {
return {
loading: false
}
},
methods: {
updateData() {
this.loading = true
uploadData().then(response => {
const data = response.data
if (data.code === 200) {
this.$message.warning({ message: 'OK', duration: 5000 })
} else {
this.$message.error({ message: data.msg, duration: 5000 })
}
this.loading = false
})
},
....
}

Related

how to make component not re-render when next page

i'm doing asynchronous processing while waiting for created to finish then start running mouted , everything is fine, but something is causing my component to re-render, looks like this: video
how do i handle the above problem
here is my code:
<template>
<div class="wrapper">
<div class="main-panel">
<dashboard-content #click.native="toggleSidebar" />
</div>
<Sidebar :sidebar-data="dataSidebar"/>
</div>
</template>
data() {
return {
dataSidebar: [],
role: adminRole.OWNER,
isPending: null, // Save promise handler
};
},
created() {
if (!(STORE_ADMIN_AUTH_KEY in this.$store._modules.root._children)) {
this.$store.registerModule(STORE_ADMIN_AUTH_KEY, store);
}
if (localStorage.getItem(ADMIN_AUTH_TOKEN_KEY)) {
const res = this.$store.dispatch(STORE_ADMIN_AUTH_KEY + "/getInfo");
this.isPending = new Promise((solver, reject) => {
res.then((data) => {
localStorage.setItem("AUTH",JSON.stringify(data.role ? data.role : adminRole.OWNER));
solver();
});
});
}
},
async mounted() {
await this.isPending;
this.getSitebarItems();
},
methods: {
getSitebarItems() {
if (localStorage.getItem("AUTH")) {
this.role = localStorage.getItem("AUTH");
}
if (this.role == adminRole.OWNER) {
this.dataSidebar = sidebarItems;
return;
}
sidebarItems.forEach((element) => {
if (element.onlyOwner == 0) {
this.dataSidebar.push(element);
}
});
},
},
thanks for your help!
Maybe you could try creating a copy of the items to prevent triggering reactivity.
getSitebarItems() {
let data = sidebarItems.slice();
if (this.role == adminRole.OWNER) {
this.dataSidebar = data;
return;
}
data = data.filter((element) => {
return element.onlyOwner == 0;
});
this.dataSidebar = data;
}

How to trigger confetti with if condition

I have a confetti that I am using it with this package: https://www.npmjs.com/package/vue-confetti
Here is my template:
<template>
<div>{{ confetti }}</div>
</template>
there is an if condition in the button which I need to render that confetti in the same condition:
But I want to use it at the top of my component because I want to use it more efficiently on the full screen.
data() {
return {
array: [],
confetti: null,
confettiEnabled: false,
};
},
methods: {
getArray() {
this.isLoading = true;
axios
.get(`api`, {
params: {},
})
.then((response) => {
this.array = response.data.data;
this.arrayLength = this.array.length;
})
.finally(() => {
this.isLoading = false;
});
this.confettiEnabled = true;
console.log(this.confettiEnabled);
},
},
mounted() {
this.getArray();
},
and I created watcher for the component:
watch: {
confetti: {
handler() {
if (this.documents.length == 0 && this.confettiEnabled) {
this.confettiEnabled = true;
this.$confetti.start();
setTimeout(() => {
this.$confetti.stop();
}, 3000);
}
},
immediate: true, // This ensures the watcher is triggered upon creation
},
},
But this logic doesnt work even and my confetti never triggers.

Countdown timer doesn't display when a new timeout props pass. How to fix it?

I want to make a special component which handles failed fetch requests. It is expected to work in this way:
If fetch request fails then several more attempts should be made after several seconds.
This special component should display countdown timer for next request to launch.
So I have:
Fetch function is in store. It works fine (makes 3 requests after 3, 6 and 9 seconds).
import { createStore } from "vuex";
const wait = async (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
export default createStore({
state: {
error: {
isError: false,
timerSec: null
}
},
mutations: {
setError(state, payload) {
state.error = payload.error;
}
},
actions: {
async fetchProducts({ commit, dispatch }, attempt = 1) {
try {
const response = await fetch("https://fakestoreapi.com/products222");
if (!response.ok) {
throw new Error("Something went wrong");
}
} catch (e) {
console.log("Request:", attempt);
commit("setError", {
error: {
isError: true,
timerSec: attempt * 3
}
});
if (attempt >= 3) {
return;
}
await wait(attempt * 3000);
return dispatch("fetchProducts", attempt + 1);
}
}
}
});
I call fetchProducts() in App.vue on mount. In App.vue I pass following data to error-request component:
<template>
<error-request v-if="error.isError" :timeout="error.timerSec"></error-request>
<h1 v-else>This should be rendered if there's no errors</h1>
</template>
In the error-request component I have a countDown method which triggered when timeout props changes.
<template>
<div>
<h1>The next attempt to fetch data will be made in:</h1>
<h2>{{ timer }}</h2>
</div>
</template>
<script>
export default {
props: ["timeout"],
data() {
return {
timer: null,
interval: null,
};
},
methods: {
countDown(sec) {
this.interval = setInterval(() => {
this.timer = sec;
if (sec === 0) {
clearInterval(this.interval);
return;
}
sec--;
}, 1000);
},
},
watch: {
timeout() {
this.countDown(this.timeout);
},
},
};
</script>
Unfortunatelly countdown timer shows only once and only on second request (it ignores first request with countdown from 3 to 1 and ignores third request. Could you help me to fix it?
I made a codesandbox: https://codesandbox.io/s/peaceful-sinoussi-ozjkq8?file=/src/App.vue
You should do:
methods: {
countDown(sec) {
this.timer = sec;
this.interval = setInterval(() => {
this.timer--;
if (this.timer === 0) {
clearInterval(this.interval);
return;
}
}, 1000);
},
},
watch: {
timeout: {
handler() {
this.countDown(this.timeout);
},
immediate: true,
},
},
There are 2 points to notice:
Don't modify the function parameters to prevent side effects (In your case is the sec parameter)
You have to trigger the watch for the first time so you need to add the option immediate: true

Don't send Axios request when user selects a result

I'm using v-autocomplete from vuetify.js to retrieve a list of values from API Server.
It works fine and my list of values is not empty.
But my problem is when I select the correct value from this list. My script sends another request to server to retrieve another autocomplete list.
Do you have any idea to avoid to send request when a result is selected by the user ? Or to send request only when a key is down ?
My component :
<template>
<div>
<v-autocomplete
v-model="selectValeur"
:loading="loading"
:search-input.sync="search"
:items="resultatsAutocomplete"
class="mb-4"
hide-no-data
hide-details
:label="recherche.label"
></v-autocomplete>
</div>
</template>
<script>
export default {
props: {
recherche: {
type: Object,
default: null,
},
},
data: () => ({
selectValeur: null,
loading: false,
search: null,
resultatsAutocomplete: [],
}),
watch: {
selectValeur(oldval, val) {
console.log(oldval)
console.log(val)
},
search(val) {
val && val !== this.selectValeur && this.fetchEntriesDebounced(val)
console.log(val)
if (!val) {
this.resultatsAutocomplete = []
}
},
},
methods: {
fetchEntriesDebounced(val) {
// cancel pending call
clearTimeout(this._timerId)
// delay new call 500ms
this._timerId = setTimeout(() => {
this.querySelections(val)
}, 500)
},
async querySelections(v) {
if (v.length > 1) {
this.loading = true
try {
const result = await this.$axios.$get(
'myapi/myurl',
{
params: {
racine: v,
},
}
)
this.resultatsAutocomplete = result
console.log(this.resultatsAutocomplete)
this.loading = false
} catch (err) {
console.log(err)
this.loading = false
}
} else {
this.resultatsAutocomplete = []
}
},
},
}
</script>
Thanks,
selectValeur would no longer be null if the user has selected a value, so you could update search() to return if selectValeur is truthy:
export default {
watch: {
search(val) {
if (this.selectValeur) {
// value already selected
return
}
//...
}
}
}
Or you could use vm.$watch on the search property to be able to stop the watcher when selectValeur is set:
export default {
mounted() {
this._unwatchSearch = this.$watch('search', val => {
val && val !== this.selectValeur && this.fetchEntriesDebounced(val)
if (!val) {
this.resultatsAutocomplete = []
}
})
},
watch: {
selectValeur(val) {
if (val && this._unwatchSearch) {
this._unwatchSearch()
}
}
}
}
I found a solution to my problem.
I used the #keyup event to send the axios request and I deleted the watcher on search.
So, the API request are only sent when I press a key.
<template>
<div>
<v-autocomplete
v-model="selectValeur"
:loading="loading"
:items="resultatsAutocomplete"
:search-input.sync="search"
class="mb-4"
hide-no-data
hide-details
:label="recherche.label"
#keyup="keyupSearch"
></v-autocomplete>
</div>
</template>
<script>
export default {
props: {
recherche: {
type: Object,
default: null,
},
},
data: () => ({
selectValeur: null,
loading: false,
resultatsAutocomplete: [],
search: '',
}),
methods: {
keyupSearch(val) {
val &&
val !== this.selectValeur &&
this.fetchEntriesDebounced(this.search)
if (!val) {
this.resultatsAutocomplete = []
}
},
fetchEntriesDebounced(val) {
// cancel pending call
clearTimeout(this._timerId)
// delay new call 500ms
this._timerId = setTimeout(() => {
this.querySelections(val)
}, 500)
},
async querySelections(v) {
if (v.length > 1) {
this.loading = true
try {
const result = await this.$axios.$get(
'my-api/my-url',
{
params: {
sid: this.$route.params.sid,
service: this.$route.params.service,
type: this.recherche.mode,
racine: v,
},
}
)
this.resultatsAutocomplete = result
console.log(this.resultatsAutocomplete)
this.loading = false
} catch (err) {
console.log(err)
this.loading = false
}
} else {
this.resultatsAutocomplete = []
}
},
},
}
</script>

Computed property "main_image" was assigned to but it has no setter

How can I fix this error "Computed property "main_image" was assigned to but it has no setter"?
I'm trying to switch main_image every 5s (random). This is my code, check created method and setInterval.
<template>
<div class="main-image">
<img v-bind:src="main_image">
</div>
<div class="image-list>
<div v-for="img in images" class="item"><img src="img.image"></div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Item',
data () {
return {
item: [],
images: [],
}
},
methods: {
fetchImages() {
axios.get(`/api/item/${this.$route.params.id}/${this.$route.params.attribute}/images/`)
.then(response => {
this.images = response.data
})
.catch(e => {
this.images = []
this.errors.push(e)
})
},
},
computed: {
main_image() {
if (typeof this.item[this.$route.params.attribute] !== 'undefined') {
return this.item[this.$route.params.attribute].image_url
}
},
},
watch: {
'$route' (to, from) {
this.fetchImages()
}
},
created () {
axios.get(`/api/item/${this.$route.params.id}/`)
.then(response => {
this.item = response.data
})
.catch(e => {
this.errors.push(e)
})
this.fetchImages();
self = this
setInterval(function(){
self.main_image = self.images[Math.floor(Math.random()*self.images.length)].image;
}, 5000);
},
}
</script>
Looks like you want the following to happen...
main_image is initially null / undefined
After the request to /api/item/${this.$route.params.id}/ completes, it should be this.item[this.$route.params.attribute].image_url (if it exists)
After the request to /api/item/${this.$route.params.id}/${this.$route.params.attribute}/images/ completes, it should randomly pick one of the response images every 5 seconds.
I'd forget about using a computed property as that is clearly not what you want. Instead, try this
data() {
return {
item: [],
images: [],
main_image: '',
intervalId: null
}
},
methods: {
fetchImages() {
return axios.get(...)...
}
},
created () {
axios.get(`/api/item/${this.$route.params.id}/`).then(res => {
this.item = res.data
this.main_image = this.item[this.$route.params.attribute] && this.item[this.$route.params.attribute].image_url
this.fetchImages().then(() => {
this.intervalId = setInterval(() => {
this.main_image = this.images[Math.floor(Math.random()*this.images.length)].image;
})
})
}).catch(...)
},
beforeDestroy () {
clearInterval(this.intervalId) // very important
}
You have to add setter and getter for your computed proterty.
computed: {
main_image: {
get() {
return typeof this.item[this.$route.params.attribute] !== 'undefined' && this.item[this.$route.params.attribute].image_url
},
set(newValue) {
return newValue;
},
},
},