Converting a function into a promise - javascript

I have a function which takes some time to complete, because of that , I want to turn it into a promise.
My goal is to be able to call the function/promise and after it is fullfilled I can use use a .then() to do whatever I want.
I want getPlantMaterials() to be resolved , after I get the API response.
I'am not sure how to correctly convert this into a promise, though :
getPlantMaterials() {
this.isLoadingPlantMaterials = true;
this.$apollo
.query({
query: gql`
query getMaterialByPotSizeId($plantPotSizeId: ID) {
tenantPlantMaterials(
plantPotSize_Id: $plantPotSizeId
) {
edges {
node {
id
quantity
material {
id
name
materialType {
name
id
}
brand
vendor
size
unit
inventory
id
}
}
}
totalCount
}
}
`,
variables: {
plantPotSizeId: this.selectedPlant.plantPotSize.id,
materialId: null,
quantity: null
}
})
.then(response => {
this.selectedPlantMaterials =
response.data.tenantPlantMaterials.edges;
this.selectedPlantMaterials.map(item => {
item.node.totalAmount =
item.node.quantity * this.totalPlantQuantity;
});
console.log(
"this.selectedPlantMaterials",
this.selectedPlantMaterials
);
})
.finally(() => {
this.isLoadingPlantMaterials = false;
});
},

Related

Getting metadata information from an Observable while executing inside another Observable

I have some song information that I need to add metadata too. I am looking to combine this using one Observable execution (sorry if my terminology is wrong). But I can't get the metadata in the final map
First Attempt:
songsViewData = combineLatest([
this.songs,
this.genre,
]).pipe(
map(([songs, genre]) => {
let query = {
filename: songs[genre],
song_id_range: 50,
filenames_included: true
}
// This doesn't return the inner object. Just an observable
return this.getSongData(query).subscribe((metaData) => {
return (songs[genre]).map((song) => {
return {
id: song,
songTitle: metaData[song].songTitle,
artistName: metaData[song].artistName
}
})
})
})
)
Second Attempt:
songsViewData = combineLatest([
this.songs,
this.genre,
]).pipe(
switchMap(([songs, genre]) => {
let query = {
filename: songs[genre],
song_id_range: 50,
filenames_included: true
}
return this.getSongData(query) // This gets the metadata
}),
map(([songs, genre, metaData]) => { // I want to access the metadata here
return (songs[genre]).map((song) => {
return {
id: song,
songTitle: metaData[song].songTitle,
artistName: metaData[song].artistName
}
})
})
)
You can use a forkJoin in which you wrap both the switchMap and also the other values you need. This only works if this.getSongData completes. Otherwise, take combineLatest instead.
songsViewData = combineLatest([
this.songs,
this.genre,
]).pipe(
switchMap(([songs, genre]) => {
let query = {
filename: songs[genre],
song_id_range: 50,
filenames_included: true
}
return forkJoin([of([songs, genre]), this.getSongData(query)])
}),
map(([[songs, genre], metaData]) => {
return (songs[genre]).map((song) => {
return {
id: song,
songTitle: metaData[song].songTitle,
artistName: metaData[song].artistName
}
})
})
)

Cannot read property 'affected_rows' of undefined when trying to run an Hasura mutation

I'm using apollo within my vue.js application, I'm currently trying to remove an object by running a mutation, here is the code :
this.$apollo.mutate({
mutation: require("../graphql/deleteTag.gql"),
variables: {
id: idToDelete,
},
update: (store, { data: { delete_tags } }) => {
if (delete_tags.affected_rows) {
const data = store.readQuery({
query: require("../graphql/fetchDevices.gql"),
});
data.device_id_to_tag_id = data.device_id_to_tag_id.filter((x) => {
return x.id != tag.device_id_to_tag_id.id;
});
store.writeQuery({
query: require("../graphql/fetchDevices.gql"),
data,
});
}
},
});
And my deleteTag.gql file :
mutation delete_tags($id: Int!){
delete_extras_taggeditem(where: { id: { _eq: $id } }) {
affected_rows
}
}
But when I run this the following error appears :
I don't really know what's going on because I followed the Hasura vue.js documentation...
Thanks in advance for your help !
You can specify the name of the returned key in graphql if you want your result data to be called just delete_extras instead of delete_extras_taggeditem:
mutation delete_tags($id: Int!){
delete_extras: delete_extras_taggeditem(where: { id: { _eq: $id } }) {
affected_rows
}
}
but right now, you query do not return you a
I believe you are missing optimisticResponse parameter in mutate. the "update" function takes 2 passes - first with data from optimisticResponse, and then the data from the actual mutation response.
e.g. something like...
this.$apollo.mutate({
mutation: require("../graphql/deleteTag.gql"),
variables: {
id: idToDelete,
},
optimisticResponse: {
delete_extras_taggeditem: {
__typename: 'extras_taggeditem',
id: -1,
affected_rows
}
},
update: (store, { data: { delete_extras_taggeditem } }) => {
if (delete_extras_taggeditem.affected_rows) {
const data = store.readQuery({
query: require("../graphql/fetchDevices.gql"),
});
data.device_id_to_tag_id = data.device_id_to_tag_id.filter((x) => {
return x.id != tag.device_id_to_tag_id.id;
});
store.writeQuery({
query: require("../graphql/fetchDevices.gql"),
data,
});
}
},
});
https://apollo.vuejs.org/guide/apollo/mutations.html#server-side-example
Also, generally speaking I would always return id in your responses back for any level of resource. Apollo relies on __typename + id to maintain and manipulate its cache.

Vue.js: Data is not reactive and not correctly updated within a method

I have a custom component which receives a list of filters in order to display just the doctors that the user has selected:
<DoctorsSidebarFilter #update-view='showFilteredDoctors'></DoctorsSidebarFilter>
Next, in my main component, I'm using this to display the doctors:
<v-flex
v-for="doctor in allDoctors"
:key="doctor.first_name"
xs12
sm6
md4
>
And here's my data:
export default {
data: () => ({
allDoctors:[],
}),
methods: {
fetchDoctors(){
//Retrieve doctors
this.$store.dispatch(RETRIEVE_DOCTORS)
.then(
response => {
this.allDoctors = response;
}
)//TODO-me: Handle the error properly!
.catch(error => {
console.log(error);
});
},
showFilteredDoctors(filters){
let result = [];
this.fetchDoctors();
console.log('1:' + " " + JSON.stringify(this.allDoctors));
if (filters.length > 0) { // If Array is not empty then apply the filters
console.log('2');
this.allDoctors.forEach(function(e) {
if(filters.some(s => s.specialty === e.specialty || s.city === e.city)) {
result.push(e);
}
});
console.log('3:' + " " + JSON.stringify(result));
this.allDoctors = [...result];
console.log('4:' + " " + JSON.stringify(this.allDoctors));
}
}
},
mounted() {
this.fetchDoctors();
}
}
The problem is that eventhough my filtering works correctly and I can see from console.log('4:' + " " + JSON.stringify(this.allDoctors)); that this.allDoctors contains the new, filtered list; this is never displayed on screen.
Instead I see the default list of doctors that I've fetched from my API. Using vue devtools I can see that the this.allDoctors is momentarily updated with the correct values but then it goes back to the default ones.
As #user1521685 has already explained, the call to fetchDoctors is asynchronous so it'll complete after you've performed the filtering.
Typically you'd do something like this using a computed property instead and only make the server call once.
export default {
data: () => ({
allDoctors: [],
filters: []
}),
computed: {
filteredDoctors() {
const allDoctors = this.allDoctors;
const filters = this.filters;
if (filters.length === 0) {
return allDoctors;
}
return allDoctors.filter(doctor => {
return filters.some(filter => filter.specialty === doctor.specialty || filter.city === doctor.city);
});
}
},
methods: {
fetchDoctors(){
//Retrieve doctors
this.$store.dispatch(RETRIEVE_DOCTORS)
.then(
response => {
this.allDoctors = response;
}
)//TODO-me: Handle the error properly!
.catch(error => {
console.log(error);
});
},
showFilteredDoctors(filters){
this.filters = filters;
}
},
mounted() {
this.fetchDoctors();
}
}
In your template you'd then use:
v-for="doctor in filteredDoctors"
fetchDoctors is async, so in showFilteredDoctors you fetch the doctors, then set the filtered array and then the thenable in fetchDoctors kicks in and overrides the doctors again: this.allDoctors = response.
You'd have to return the Promise in fetchDoctors and use it in showFilteredDoctors like so:
this.fetchDoctors().then(() => /* do the filtering */)
EDIT: Return the Promise like this:
return this.$store.dispatch(RETRIEVE_DOCTORS).then().catch()

How to update values in table with this.state?

I make a component, which show information from database in table. But this information with filters.
Filtering can be by event type and by participant (id: integer type).
When I click the button, I call handleShowClick(). In this function I check: if value of type event isn't null, I get from database events with this type. if value of type event is null, I get all events.
After this I check a participant value. If value isn't null, I call function, which search which events are include this participant. Data from this.state.event show in table in another component.
I haven't problems with event type. But I have problem with participant. When I choose one of participant, table shows correct data for a split second. After this return to prev state (without filter by participants).
How can I fix this issue? I set state to event only in this component
class TestPage extends Component {
constructor(props) {
super(props);
this.state = {
event: [],
searchByType: null,
searchByParticipant: null,
participantToEvent: []
};
this.handleShowClick = this.handleShowClick.bind(this);
this.onHandleEventByTypeFetch = this.onHandleEventByTypeFetch.bind(this);
this.handleParticipantSearch = this.handleParticipantSearch.bind(this);
this.onHandleEventFetch = this.onHandleEventFetch.bind(this);
}
handleShowClick() { // onClick
if (this.state.searchByType !== null) {
this.onHandleEventByTypeFetch(); // select * from ... where type=...
} else {
this.onHandleEventFetch(); // select * from ...
}
if (this.state.searchByParticipant !== null) {
this.handleParticipantSearch();
}
}
handleParticipantSearch() {
const list = [];
this.state.participantToEvent.map(itemP => { // participantToEvent is binding table
if (itemP.parid === this.state.searchByParticipant) {
this.state.event.map(itemEvent => {
if (itemEvent.id === itemP.eventid) {
list.push(itemEvent);
}
});
}
});
console.log(list); // here I see array with correct result
this.setState({ event: list });
}
onHandleEventFetch() {
fetch( ... , {
method: 'GET'
})
.then((response) => {
if (response.status >= 400) {
throw new Error('Bad response from server');
}
return response.json();
})
.then(data => {
if (data.length === 0) {
alert('nothing');
} else {
this.setState({
event: data
});
}
});
}
onHandleEventByTypeFetch() {
fetch( ... , {
method: 'GET'
})
.then((response) => {
if (response.status >= 400) {
throw new Error('Bad response from server');
}
return response.json();
})
.then(data => {
if (data.length === 0) {
alert('nothing');
} else {
this.setState({
event: data
});
}
});
...
}
}
Structure of this.state.event:
[{id: 1, name: 'New event', participant: 5, type: 10}, ...]
Structure of this.state.participantToEvent:
[{id: 1, idparticipant: 5, idevent: 1}, ...]
this.setState(...this.state,{ event: list });
I think this would solve your problem. Because you clear every item except for {event:list} by not copying the previous state.
Edit:
You should put
...this.state
to onHandleEventByeTypeFetch and onHandleEventFetch. Without them when you click handleShowClick one of those two functions always work and clears searchByParticipant data from the state by not copying the previous state.
The reason for you see the correct data for a short time is all about async nature of the state.

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);
});
}
}
}

Categories