How to change/trigger HTML DOM after getting ajax response in Vue - javascript

I am new to Vue. I struggling and trying last half day not got any solution.
Here, I need to change todos text automatically based ajax response.
Using setInterval need to update vue instance and change HTML DOM as well.
When I update todo object, can't change the DOM automatically
<div id="app">
<ul>
<li v-for="question in todos.text">
{{ question.text }}
</li>
</ul>
</div>
<script>
var app = new Vue({
el: '#app',
data: function () {
return {
message: 'You loaded this page on ' + new Date().toLocaleString(),
todos:
{
Event: 'Event1',
text: [
{ text: 'Learn JavaScript1' },
{ text: 'Learn Vue1' },
{ text: 'Build something awesome1' }
]
}
}
},
mounted: function() {
setInterval(function () {
axios({
method: 'post',
url: 'test.php',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
}).then(response => {
console.log(response.data);
this.todos = response.data;
Vue.set(this, todos, response.data );
})
.catch(err => {
console.log(err);
});
}, 5000);
}
})
</script>

The scope of this is bound to Window instead of your Vue instance.
mounted: function() {
console.log(this); // Vue
setInternval(function() {
console.log(this); // Window
}, 1000);
setInterval(() => {
console.log(this); // Vue
}, 1000);
}
You had the right idea with your axios promises, .then(response => { .. }) in using the arrow function to preserve the scope of this but you didn't apply it to setInterval.
If for some reason you really like the look of setInterval(function() { .. }), or you need this to be the Window object, you can create a variable and assign it to this outside of the setInterval function.
mounted: function() {
const vThis = this; // Vue
setInterval(function() {
axios({..})
.then(response => {
vThis.todos = response.data;
console.log(this); // Window
console.log(vThis); // Vue
})
.catch(error => {
});
}, 5000);
}

Related

how to prevent a re-rendering of a variable that is not being used in the HTML of my vue.js component?

I am trying to recreate a real example of my code.
In my real code, this line is actually a component that will fetch an endpoint every few seconds, and fetch a random array of "n" length, myData it will contain these fetch.
<div v-for="item in addingData(myData)"> <!-- in My real code, "myData" should be the answer of an endpoint, is an setInterval, returns data like [{id:1},{id:2}] -->
{{ item.id }}
</div>
I am simulating that the response changes in myData with the help of setTimeOut
mounted() {
setTimeout(() => {
console.log('First data');
this.myData = [{ id: 3 }, { id: 2 }, { id: 1 }];
setTimeout(() => {
console.log('second data');
this.myData = [{ id: 4 }, { id: 4 }];
setTimeout(() => {
console.log('Third data');
this.myData = [];
}, 3000);
}, 3000);
}, 2000);
},
I am trying to make that every time I receive data in myData, the list of the concatenation of the received data is shown without having repeated data. That's why every time I receive data, that calls the function addingData(myData) that will do this data concatenation.
I'm using the function v-for="item in addingData(myData) and auxData is the variable that will do this concatenation.
why when there is new data, the addingData function is called 2 times and how can I prevent it?
in terms of performance this should be the output in the console.log:
what causes this re-rendering and how can I avoid it?
this is my live code:
https://stackblitz.com/edit/vue-l7gdpj?file=src%2FApp.vue
<template>
<div id="app">
<div v-for="item in addingData(myData)">
{{ item.id }}
</div>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue';
export default {
name: 'App',
data() {
return {
myData: [],
auxData: [],
};
},
mounted() {
setTimeout(() => {
console.log('First data');
this.myData = [{ id: 3 }, { id: 2 }, { id: 1 }];
setTimeout(() => {
console.log('second data');
this.myData = [{ id: 4 }, { id: 4 }];
setTimeout(() => {
console.log('Third data');
this.myData = [];
}, 3000);
}, 3000);
}, 2000);
},
methods: {
addingData(getDataFetch) {
console.log('Entering AddingData', getDataFetch);
if (getDataFetch.length !== 0) {
if (this.auxData.length === 0) {
//Adding initial data
this.auxData = getDataFetch;
} else {
//prevent duplicated values
getDataFetch.forEach((item) => {
const isNewItem = this.auxData.find((itemAux) => {
return item.id === itemAux.id;
});
if (!isNewItem) {
//adding new data
this.auxData.unshift(item);
}
});
}
} else {
//if there is not data, return []
return this.auxData;
}
},
},
};
</script>
As per my understanding, You want to combined the unique objects in to an array getting from multiple API calls and show them into the template using v-for. If Yes, You can achieve that by using computed property.
As you are updating the myData every time you are getting response, You can push the unique objects into a separate array and then return that array using a computed property.
Live Demo :
new Vue({
el: '#app',
data: {
combinedData: [],
myData: []
},
mounted() {
setTimeout(() => {
console.log('First data');
this.myData = [{ id: 3 }, { id: 2 }, { id: 1 }];
this.pushData(this.myData)
setTimeout(() => {
console.log('second data');
this.myData = [{ id: 4 }, { id: 4 }];
this.pushData(this.myData)
setTimeout(() => {
console.log('Third data');
this.myData = [];
this.pushData(this.myData)
}, 3000);
}, 3000);
}, 2000);
},
methods: {
pushData(data) {
data.forEach(obj => {
if (!JSON.stringify(this.combinedData).includes(JSON.stringify(obj))) {
this.combinedData.push(obj)
}
});
}
},
computed: {
finalData() {
return this.combinedData
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item in finalData">
{{ item.id }}
</div>
</div>
in terms of performance this should be the output in the console.log
In terms of performance, you should use as few reactive data as possible, especially if your object has many properties. I would modify auxData directly.
this.addingData([{ id: 3 }, { id: 2 }, { id: 1 }]);
Simplified addingData
addingData(getDataFetch) {
// It's faster to get the id-s first
let itemDict = new Set(this.auxData.map((m) => m.id));
getDataFetch.forEach((item) => {
if (!itemDict.has(item.id)) {
this.auxData.unshift(item);
itemDict.add(item.id);
}
});
},
And iterate over it
<div v-for="item in auxData">
{{ item.id }}
</div>
Also watching object list can also cause performance issues. It should be used on primitive values.
Example on StackBlitz
Looks like you should be using v-for with auxData as that's what you're updating using the result of your API call (myData). As your API sends you new results, use a watcher to run a function whenever a new update is made to then also update auxData
updated stackblitz
watch: {
myData(newData, oldData) {
console.log('Entering AddingData', newData);
if (newData.length !== 0) {
if (this.auxData.length === 0) {
this.auxData = newData;
} else {
newData.forEach((item) => {
const isNewItem = this.auxData.find((itemAux) => {
return item.id === itemAux.id;
});
if (!isNewItem) {
this.auxData.unshift(item);
}
});
}
}
},
},
<div v-for="item in auxData">
{{ item.id }}
</div>

Update state after using dispatch

Im using vuex and I have an action
storeExpense(context, params){
axios.post('api/expenses', params)
.then( response => {
console.log("Expense Created");
})
.catch( error => {
console.log(error);
});
}
and on my Expense.vue im using the action via
this.$store.dispatch('storeExpense',this.expense)
.then( response => {
this.modalShow = false
this.$swal(
'Success',
'Expense has been created!',
'success'
)
})
I dont have an error but after the expense was created the state is not updating therefore I need to refresh the page in order for my table to get the latest data.
I have a mutation called
mutateExpenses(state, payload){
state.expenses = payload
}
however when i use this after the response it overrides the whole state.expenses object to a single object because this.expense is a single object
Im new to vuex.
You must update your store using mutations that are called inside your actions.
I suggest you to dive a bit into the Vuex documentation, especially the mutations and actions :)
Here is an example of how to use the store :
It goes dispatch --> action --> mutation
// Your store
const store = new Vuex.Store({
state: {
posts: [],
isLoading: false
},
mutations: {
// Must be called by actions AND ONLY by actions
add(state, post) {
// Add the given post to the 'posts' array in our state
Vue.set(state.posts, state.posts.length, post)
},
busy(state) {
Vue.set(state, 'isLoading', true)
},
free(state) {
Vue.set(state, 'isLoading', false)
}
},
actions: {
create({
commit
}, post) {
commit('busy')
axios.post('https://jsonplaceholder.typicode.com/posts', post)
.then(response => {
// Call the mutation method 'add' to add the newly created post
commit('add', response.data)
})
.catch((reason) => {
// Handle errors
})
.finally(() => {
commit('free')
});
},
}
})
// Your Vue app
new Vue({
el: "#app",
store,
data: {
post: {
title: 'foo',
body: 'bar',
userId: 1
}
},
methods: {
onButtonClicked() {
this.$store.dispatch('create', this.post)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.0/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<button #click="onButtonClicked">Create Post</button>
<div>Posts : <span v-if="$store.state.isLoading">Requesting</span></div>
<div v-for="post in $store.state.posts">
{{post}}
</div>
</div>

Computed method don't recognize updated data

I'm trying to use a computed method total this calculate the number of word and multiply to a price.
The price is obtained with a method accessing an API.
But the computed method don't use the updated data price. Its returning empty.
var app = new Vue({
el: '#app',
data: {
text: '',
qualidade: '',
selected: '',
options: [],
lang1: '',
lang2: '',
ola: '',
price: ''
},
beforeCreate: function() {
axios.get('/languages.json')
.then((response) => {
this.options = response.data
})
},
computed: {
total: function() {
return (this.words * this.preco).toLocaleString('de-DE')
},
words: function() {
if(this.text.length == 0) {
return 0
} else {
this.words = this.text.split(' ').length
console.log(this.words)
return this.text.split(' ').length
}
}
},
methods: {
price: function () {
axios.post('/service/price', {
lang_origem: this.lang1,
lang_dest: this.lang2
})
.then(function (response) {
this.preco = response.data.price
console.log(this.price)
})
.catch(function (error) {
console.log(error);
});
}
}
})
Problems I am able to see in your codes,
Both data and methods have a property named price, they would clash.
preco is not reactive. If it's not reactive, changing its value will not update the computed values which depends on it. You should add preco to data to make it reactive.
You should use arrow function in the axios request. Otherwise, this in this.preco = ... would not be referring to the Vue instance
this.preco will be empty as long the server call ( axios.post('/service/price' ...) is not finished you need to rewrite this to a method that updates the this.total
Something like this:
{
methods: {
calcTotal: function () {
this.price()
.then(() => {
this.total = (this.words * this.preco).toLocaleString('de-DE')
})
},
price: function () {
//return so that we can wait on this to be finished
return axios.post('/service/price', {
lang_origem: this.lang1,
lang_dest: this.lang2
})
.then(function (response) {
this.preco = response.data.price
console.log(this.price)
})
.catch(function (error) {
console.log(error);
});
}
}
}

Get Vue to update view/component

I'm stuck at a crossroads with a component I am working on.
I have the following component "RecentUpdates"
Within it I am passing props down to a few other components, as you can see from the top of the file.
My problem is when adding a new post, I can not figure out how to get the correct update object array back and i also can not figure out the correct 'Vue way' to update the data prop that is being passed down to the "PostList" component.
<template>
<div>
<PostFilter v-on:selectedCategory="getSelectedPosts" v-on:showAllPosts="showAllPosts" :user="user" :categories="categories"/>
<PostList v-if="recent_posts[0]" :categories="categories" :posts="recent_posts[0]" :user="user"/>
<Pagination v-on:getPreviousPage="getPreviousPage" v-on:getNextPage="getNextPage"/>
</div>
</template>
<script>
import PostList from './PostList';
import PostFilter from './PostFilter';
import Pagination from './Pagination';
import EventBus from '../event-bus';
export default {
name: 'RecentUpdates',
data: () => ({
errors: [],
recent_posts: [],
}),
props: ['categories', 'user'],
components: {
PostList,
PostFilter,
Pagination
},
created() {
if (this.user.meta.selected_categories[0] == 0) {
this.showAllPosts();
}
// do not call here, not working as expected
// is switching selected category to an incorrect one
// this.updateList();
this.getSelectedCategory();
},
watch: {
recent_posts: function(newValue) {
EventBus.$on('addPost', function(newPost) {
console.log(newPost);
this.$forceUpdate();
//this.recent_posts.push(newPost);
//this.$set(this.recent_posts, newPost, newPost);
// this.$nextTick(function () {
// this.recent_posts.push(newPost);
// });
});
console.log(this.recent_posts[0]);
// this.$nextTick(function () {
// console.log(this.recent_posts[0]) // => 'updated'
// });
// if (this.user.meta.selected_categories[0] == 0) {
// EventBus.$on('addPost', this.showAllPosts);
// } else {
// EventBus.$on('addPost', this.getSelectedCategory);
// }
//this.updateList();
}
},
methods: {
// updateList() {
// if (this.user.meta.selected_categories[0] == 0) {
// EventBus.$on('addPost', this.showAllPosts);
// //EventBus.$emit('newPost');
// } else {
// EventBus.$on('addPost', this.getSelectedCategory);
// //EventBus.$emit('newPost');
// }
// },
getSelectedCategory() {
let categoryId = this.user.meta.selected_categories[0];
this.getSelectedPosts(categoryId);
},
showAllPosts() {
axios.get('/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]',
{headers: {'X-WP-Nonce': portal.nonce}})
.then(response => {
this.recent_posts = [];
//this.recent_posts = response.data;
//console.log(response.data);
this.recent_posts.push(response.data);
console.log(this.recent_posts[0]);
})
.catch(e => {
this.errors.push(e);
});
},
getSelectedPosts(categoryId) {
axios.get('/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]&categories=' + categoryId,
{headers: {'X-WP-Nonce': portal.nonce}})
.then(response => {
this.recent_posts = [];
//console.log(response.data);
this.recent_posts.push(response.data);
console.log(this.recent_posts[0]);
})
.catch(e => {
this.errors.push(e);
});
},
/**
* Pagination methods
*
*/
getPreviousPage(page) {
axios.get('/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]&page=' + page,
{headers: {'X-WP-Nonce': portal.nonce}})
.then(response => {
this.recent_posts = response.data;
})
.catch(e => {
this.errors.push(e);
});
},
getNextPage(page) {
axios.get('/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]&page=' + page,
{headers: {'X-WP-Nonce': portal.nonce}})
.then(response => {
this.recent_posts = response.data;
})
.catch(e => {
this.errors.push(e);
});
}
},
}
</script>
<style>
</style>
So there are a number of issues I see reading through your code.
You have a recent_posts data property, which is an array. When you make your ajax call to get the posts you push the response which is also an array into the recent_posts array. Why? Why not just set recent_posts = response.data? Then you won't have to be passing recent_posts[0] around.
You're setting up your EventBus handler inside a watcher. This is really unusual. Typically you would set up a handler inside created or mounted.
this inside the EventBus handler likely refers to the EventBus and not your Vue. Ideally, you would set the handler to be a method on the component, which is already bound to the Vue. Something like EventBus.$on("addPost", this.addPost).
Once you've done all that, adding a new post should be as simple as this.recent_posts.push(newPost).
Here is what I might recommend.
export default {
name: 'RecentUpdates',
data(){
return {
errors: [],
recent_posts: []
}
},
props: ['categories', 'user'],
components: {
PostList,
PostFilter,
Pagination
},
created() {
if (this.user.meta.selected_categories[0] == 0) {
this.showAllPosts();
}
this.getSelectedCategory();
EventBus.$on("addPost", this.addPost)
},
beforeDestroy(){
EventBus.$off("addPost", this.addPost)
},
methods: {
getPosts(url){
axios.get(url, {headers: {'X-WP-Nonce': portal.nonce}})
.then(response => this.recent_posts = response.data)
.catch(e => this.errors.push(e))
},
showAllPosts() {
const url = '/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]';
this.getPosts(url);
},
getSelectedPosts(categoryId) {
const url = '/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]&categories=' + categoryId;
this.getPosts(url);
},
addPost(newPost){
this.recent_posts.push(newPost)
},
... //other methods
},
}
Try using kebab-case in your event listeners instead of camelCase:
Example: v-on:selectedCategory="getSelectedPosts" should be v-on:selected-category="getSelectedPosts".
Example: v-on:showAllPosts="showAllPosts" should be v-on:show-all-posts="showAllPosts" or even using the shortcut #show-all-posts="showAllPosts".
UPDATE: If you can provide the code of the other components so we can have a clearer vision of your problem, But you only want to track changes that happens on an object or an array in vue.js you need to deep watch them.
your watcher should be :
watch: {
recent_posts: {
deep: true,
handler: function( oldValue, newValue) {
console.log( "recent_posts has changed" );
// A post has been added, updated or even deleted
}
}
}

vue.js auto reload / refresh data with timer

(New to Vue.js) I fetch data from a get request to display calendar information. I want this to update every 5 minutes.
Nothing in the docs about auto reload - how would I go about implementing this? Do I use standard javascript within the file or something else?
My complete app.js below:
Vue.component('events', {
template: '#events-template',
data: function() {
return {
list: []
}
},
created: function() {
this.fetchEventsList();
},
methods: {
fetchEventsList: function() {
this.$http.get('events', function(events) {
this.list = events;
}).bind(this);
}
}
});
new Vue({
el: 'body',
});
No need to re-invent the wheel, window.setInterval() does the job pretty well
Vue.component('events', {
template: '#events-template',
data () {
return {
list: [],
timer: ''
}
},
created () {
this.fetchEventsList();
this.timer = setInterval(this.fetchEventsList, 300000);
},
methods: {
fetchEventsList () {
this.$http.get('events', (events) => {
this.list = events;
}).bind(this);
},
cancelAutoUpdate () {
clearInterval(this.timer);
}
},
beforeUnmount () {
this.cancelAutoUpdate();
}
});
new Vue({
el: 'body',
});

Categories