Update data without refresh the page with Vue and axios - vue.js

I have a page with 2 tabs (Questions and Data) made on Vue and Axios.
In first Tab, I fill the form and submit it - Save button v-on:click="save".
save: function() {
axios({
method: 'patch',
url: url,
data: this.data
})
.then(function (response) {
this.data = response.data;
}
In the second Tab(Data) I have the list of saved data:
mounted() {
axios
.get('/api/recommended-products/?patient_uuid=' + '{{patient.uuid}}')
.then(response => (this.data= response.data.results))
}
Now when I change answers in Questions Tab my list in Data Tab should change automatically. It changes if I refresh the page - mounted() works.
I tried to create updateList() function:
updateList: function() {
axios
.get('/api/recommended-products/?patient_uuid=' + '{{patient.uuid}}')
.then(response => (this.data= response.data.results))
}
and added it to save() function like:
save: function() {
axios({
method: 'patch',
url: url,
data: this.data
})
.then(function (response) {
this.data = response.data;
this.updateList();
}
The problem is that this way works other second time (sometime works sometimes not). So I just added location.reload(); to save() but I don't like this approach. Is it possible to update Data list without refreshing the page? What am I doing wrong with updateList() function?

In my opinion here you should use vuex and its getters.
You would then have only one request in all the application and the data would be automatically refreshed once updated in the state.
You can then access the data using a computed property which will be automatically re-rendered when the state is updated.
Here is an example using multiple tabs : https://codesandbox.io/s/vuex-axios-demo-forked-m0cqe4?file=/src/App.vue
In this example, i'm loading posts information through the JsonPlaceHolder API.
Every time the form is re send (using a function). The action of the store is called, then the state is updated which trigger the getter to re-render the data.
Note: i'm loading the first post into the mounted with a default value of 1 here.

save: function() {
axios({
method: 'patch',
url: url,
data: this.data
})
.then(function (response) {
this.data = […this.data, …response.data]
}
You have re rendered issue I think can can you try above solution

I think this might be helpful. Try to implement something like following.
async function() {
try{
await axios.post() // or any request
//action if success
//another action if success
...
} catch(error) {
//do something with error.
console.log(error)
}

Related

Vue.js render new data from API call

I am making a weather app written in Vue.js, which fetches weather data periodically, but I have an issue rendering new data after the initial API call.
The empty data array is declared in the data, and a timer is used to fetch new data, as such:
data() {
return {
weatherData: [],
timer: '',
};
},
I have declared the data fetching in methods, as such:
methods: {
async fetchWeatherData() {
const response = await fetch("http://localhost:5002/timeseries");
const data = await response.json();
if (response.ok) {
console.log("Data fetched sucssefully!");
}
return data;
},
And when the app loads, the fetchWeatherData and setInterval is initiated:
async created() {
this.weatherData = await this.fetchWeatherData();
setInterval(() => this.timer = this.fetchWeatherData(), 10000)
},
The problem is that the new data is not rendered to the DOM, although new data is fetched successfully.
What would be the best step to ensure that the new data is rendered correctly upon successfull fetch?
-HK
In the component (or any container) where you render the weather data, add a key (like :key="renderWeatherKey"). Add renderWeatherKey to component data.
data() {
return {
weatherData: [],
timer: '',
renderWeatherKey: 0
};
},
In the method fetchWeatherData(), inside 'if' condition, add this.renderWeatherKey++ :
async fetchWeatherData() {
const response = await fetch("http://localhost:5002/timeseries");
const data = await response.json();
if (response.ok) {
console.log("Data fetched sucssefully!");
this.renderWeatherKey++
}
return data;
},
You can force the re rendered with that.
The solution, as posted by James Thomson over at the Vue forum, was to set the setInterval to async, since the fetch method also is async. See the full answer here.

VueJS: $router.push not working with query parameters

In my NuxtJS(v. 2.10.2) application, I have a URL like below where pid is a post's id.
/post?pid=5e4844e34202d6075e593062
This URL works fine and loads the post as per the value passed to the pid query parameter. However, user can add new post by clicking Add Post button on the application bar that opens a dialog. Once the user clicks add, a request to back-end server is made to save the request. And once successful, user is redirected to the new post using vue router push like below
.then(data => {
if (data) {
this.$router.push({ path: `/post?pid=${data.id}` });
}
})
The problem is, user is not redirected to the new post, only the query parameter pid is updated. I suspect VueJS does not acknowledge this as a different URL and hence does nothing.
How to fix this?
Update: As an alternative tried the syntax below but getting the same behavior.
this.$router.push({ path: "post", query: { pid: data.id } });
Say you have a component post.vue which is mapped with /post URL.
Now if you redirect the user to /post?pid=13, the post.vue component won't mount again if it's already mounted ie. when you are already at /post or /post?pid=12.
[1] In this case, you can put a watch on the route to know if the route has been changed.
watch: {
'$route.path': {
handler (oldUrl, newUrl) {
let PID = this.$route.query.pid
// fetch data for this PID from the server.
// ...
}
}
}
OR
[2] If the component post.vue is mapped with some route say /post.
You can also use the lifecycle -> beforeRouteUpdate provided by vue-router
beforeRouteUpdate (to, from, next) {
let PID = to.query.pid
// fetch data for this PID from the server.
// ...
next()
}
By changing the approach component data can be updated as per the new query string value. Here is how it can be done.
Rather than trying to push to the same page again with different query string. The query string pid itself can be watched for change and on update new data can be fetched and the component data can be updated. In NuxtJS(v. 2.10.2) apps, this can be achieved with watchQuery. watchQuery is a NuxtJS property which watches changes to a query strings. And once it detects the change, all component methods(asyncData, fetch, validate..) are called. You can read more https://nuxtjs.org/api/pages-watchquery/
As for the solution, pushing to the same page with new query string remains the same.
.then(data => {
if (data) {
this.$router.push({ name: 'post', query: { pid: data.id } });
}
})
However, on the page.vue, where the data is fetched from the server. We need to add watchQuery property.
watchQuery: ["pid"],
async asyncData(context) {
let response = await context.$axios.$get(
`http://localhost:8080/find/id/${context.route.query.pid}`
);
return { postData: response };
},
data: () => ({
postData: null
})
Now, everytime the query string pid will change asyncData will be called. And that is it. An easy fix to updating component data when the query string value change.
try this solution
.then(data => {
if (data) {
this.$router.push({ name: 'post', query: { pid: data.id } });
}
})
hints:
// with query, resulting in /register?plan=private
router.push({ path: 'register', query: { plan: 'private' } })
Use watchQuery property (https://nuxtjs.org/docs/2.x/components-glossary/pages-watchquery)
export default {
watchQuery: true,
data: () => ...
}
In case anybody was looking for this:
Query parameters specified as a string do not work when passed to a path parameter:
router.push({path: 'route?query=params'})
When you want you use them as a string, just pass the whole string as an argument, like so: router.push('route?query=params')
It'll then be automagically picked by router and navigation will happen.
try this :
.then(data => {
if (data) {
this.$router.push('/post?pid=' + data.id);
}
})
hope it works!!!

Debounce Vuex Action Call to Database Not Working

I have a few components that can be separate or on the same page. Each of these components uses the same Vuex state. Since they can each be used on other pages and still work, each of them dispatches a call to the same Vuex action which in turns calls a service that uses axios to get the JSON data.
All of this works great!
However, when I do have 2 (or more) of these components on a single page, that axios call gets called 1 time for each of the components. Initially, I went down the path of trying to see if data existed and get created a "last got data at" timestamp so I could just bypass the 2nd call. However, these are happening both on the components created event and are being essentially called at the same time.
So, enter debounce. Seems like the exact reason for this. However, when I implement it, it fails and is passing on to the next line of code and not awaiting. What am I doing wrong?
Agenda Component (one that uses the same state)
async created() {
await this.gatherCalendarData();
},
methods: {
async gatherCalendarData() {
await this.$store.dispatch('time/dateSelected', this.$store.state.time.selectedDate);
},
},
Month Component (another, notice they are the same)
async created() {
await this.gatherCalendarData();
},
methods: {
async gatherCalendarData() {
await this.$store.dispatch('time/dateSelected', this.$store.state.time.selectedDate);
},
},
The Action getting called
async dateSelected(context, data) {
let result = await getCalendarData(isBetween.date, context.rootState.userId);
await context.commit('SET_MONTHLY_DATA', { result: result.Result, basedOn: isBetween.date });
},
This getCalendarData method is in a service file I created to make api calls (below.)
This is the error that I receive (once for each component) that calls this action.
[Vue warn]: Error in created hook (Promise/async): "TypeError: Cannot read property 'Result' of undefined"
Which is referring to the 3rd line above: result: result.Result
API Service
const getCalendarData = debounce(async (givenDate, userId) => {
let response = await getCalendarDataDebounced(givenDate, userId);
return response;
}, 100);
const getCalendarDataDebounced = async (givenDate, userId) => {
let result = await axiosGet('/api/v2/ProjectTime/BuildAndFillCalendarSQL', {
givenDate: givenDate,
userID: userId,
});
return result;
};
Axios Wrapper
const axiosGet = async (fullUrl, params) => {
let result = null;
try {
let response = await axios.get(fullUrl, params ? { params: params } : null);
result = await response.data;
} catch(error) {
console.error('error:', error);
}
return result;
};
If I put console.log messages before, after and inside the getCalendarData call as well as in the getCaledarDataDebounced methods: (assuming just 2 components on the page) the 2 before logs show up and then the 2 after logs appear. Next the error mentioned above for each of the 2 components, then a single 'inside the getCalendarData' is logged and finally the log from within the debounced version where it actually gets the data.
So it seems like the debouncing is working in that it is only run a single time. But it appears that await call let result = await getCalendarData(isBetween.date, context.rootState.userId); is not truly Waiting.
Am I missing something here?
EDITS after Answer
Based on #JakeHamTexas' answer, my action of dateSelected is now (actual full code, nothing removed like above as to not confuse anything):
async dateSelected(context, data) {
console.log('dateSelected action');
let isBetween = isDateWithinCurrentMonth(data, context.state);
if (!isBetween.result) {
// The date selected is in a different month, so grab that months data
return new Promise(resolve => {
getCalendarData(isBetween.date, context.rootState.userId)
.then(result => {
console.log('inside promise');
context.commit('SET_MONTHLY_DATA', { result: result.Result, basedOn: isBetween.date });
context.commit('SET_SELECTED_DATE', isBetween.date);
context.commit('statistics/TIME_ENTRIES_ALTERED', true, { root: true });
resolve();
});
});
} else {
// The date selected is within the given month, so simply select it
context.commit('SET_SELECTED_DATE', data);
}
context.commit('CLEAR_SELECTED_TIME_ENTRY_ID');
},
And my API call of getCalendarData is now:
const getCalendarData = async (givenDate, userId) => {
console.log('getting calendar data');
let result = await axiosGet('/api/v2/ProjectTime/BuildAndFillCalendarSQL', {
givenDate: givenDate,
userID: userId,
});
return result;
};
The error is gone! However, it does not seem to be debouncing - meaning everything gets called 3 times. I would expect the dateSelected action to be called 3 times. But I would like to avoid the getting calendar data being called 3 times. If it helps, this is what the console looks like:
dateSelected action
getting calendar data
dateSelected action
getting calendar data
dateSelected action
getting calendar data
inside promise
inside promise
inside promise
You need to return a promise from your action. Returning a promise of undefined (which is what is currently happening) resolves immediately.
dateSelected(context, data) {
return new Promise(resolve => {
getCalendarData(isBetween.date, context.rootState.userId)
.then(result => {
context.commit('SET_MONTHLY_DATA', { result: result.Result, basedOn: isBetween.date });
resolve();
}
}
},
Additionally, a vuex commit does not return a promise, so it doesn't make sense to await it.

Why declared field in data with props initial value is undefined?

Since mutating a prop is an antipattern I do the following as one of the solutions to that, however when I console.log my new data field I get undefined. What's wrong?
export default {
name: "modal",
props: ["show"],
data() {
return {
sent: false,
mutableShow: this.show
};
},
methods: {
closeModal: function() {
this.mutableShow = false;
},
sendTeam: function() {
var self = this;
let clientId = JSON.parse(localStorage.getItem("projectClient")).id;
axios({
method: "get",
url: "/send-project-team/" + clientId,
data: data
})
.then(function(response) {
self.sent = true;
$("h3").text("Wooo");
$(".modal-body").text("Team was sent succesfully to client");
setTimeout(function() {
console.log(this.mutableShow);
self.closeModal();
}, 3000);
})
.catch(function(error) {
console.log(error);
});
}
}
};
Your timeout handler is establishing a new context. Instead of
setTimeout(function() {
console.log(this.mutableShow);
self.closeModal();
}, 3000);
you could use
setTimeout(() => {
console.log(this.mutableShow);
self.closeModal();
}, 3000);
And you'd need to make a similar change to
.then(function(response) {
to
.then(response => {
having said that, though, I'm not sure the code is going to behave as you might want it. Once the users closes the modal, it won't be possible to open it again since there is no way to make mutableShow equal to true.
Edited to add:
Since you're defining the self variable, you could also use that.
console.log(self.mutableShow);
Edited to add:
Without knowing specifically what behavior is intended, the best suggestion I can offer is to follow accepted Vue practices. Namely, after the AJAX request succeeds, emit a custom event. Have the parent component listen for that event and, when triggered, change the show prop.

Emit Events and On Vuejs 2

Hi this is my parent custom events
bus.$emit('updated-users', 'Sample Data')
My component
bus.$on('updated-users', data => this.lastname = data)
This one works.
Using Callback doesn't work.
bus.$on('updated-users', function(data){
//data is displayed in the console log.
console.log(data)
//This doesn't work.
this.fetchedUsersData()
})
I need to trigger a method but it doesn't work. I can't find any example that trigger a method. Please help.
Can you try this
created () {
bus.$on('updated-users', fetchedUsersData)
},
methods: {
fetchedUsersData(){
//the fetched user data
console.log('the fetched user data')
}
}