EDIT: Added extra code in the filterEvents snippet for more context.
I'm not quite understanding what's going on with my code. I'm trying to pass an array into an action function inside of my Vuex store. If I return a Promise inside of that action function, then the parameter being passed isn't of type Array and is instead an Object, which results in the reject() error that I have for the Promise.
Here's some code for context:
filterEvents({ commit }, events) {
console.log(Array.isArray(events)); //this ends up false
console.log(events);
return new Promise((resolve, reject) => {
if (!Array.isArray(events)) {
reject("Invalid argument: is not of type Array.");
}
let filtered = events.filter((event) => {
let now = new Date();
let event_stop = new Date(event.stop_time);
if (event_stop >= now || event_stop == null) {
return event;
}
});
resolve(filtered);
});
}
Here's where I call filterEvents; inside of getEvents;
getEvents({ state, commit, dispatch }, searchParams) {
.....
eventful.getEvents(searchParams).then(async (res) => {
.....
console.log(Array.isArray(res.data.events.event)); //this ends up true
console.log(res.data.events.event);
/* where I call it */
await dispatch("filterEvents", res.data.events.event).then((res) => {
.....
});
}).catch((err) => {
.....
});
}
Here's the output from the Chrome developer console. First two outputs are from getEvents and last two are from filterEvents
Would really like an explanation as to why this is the case. I'm going to bet it's something small, but it's 3 a.m. at the moment and my brain can't wrap around why it's not of type Array when passed into filterEvents.
I always try to check the length prop of the array which helps me out in such cases.
...
return new Promise((resolve, reject) => {
if (!Array.isArray(events) && !events.length) {
reject("Invalid argument: is not of type Array.");
}
.....
});
...
I finally understood what my issue was after taking another look at the object that was being logged on the console. I did not know that Vuex actions HAD to have two arguments if you want to pass in a payload into that function. For example, I initially did this
filterEvents(events) {
.....
}
but what I really needed to do was
filterEvents(context, events) {
.....
}
The context argument is the object that allows you to do things such as commit and dispatch. I usually destructure the context object (i.e. { commit, dispatch} ), so I for some reason never thought twice about it. You don't have to destructure the context object to use commit and dispatch; if you don't it would just be like
context.commit('function', payload);
Related
So, i am new to RXJS, and i have checked a lot of stackoverflow and documentation before coming here and asking this, but i'm finding a hard time to make my logic work.
I have an Observable that will fetch a collection of documents and return them, and i use the pipe operator to make some changes, like using the map operator to change the object. So far, everything is fine.
The problem is here. Afterward, i need to run an "http request" for every document, in order to get specific data about them ("tags"). The http request is of course made as an Observable too, that needs to get subscribed on to fetch the data. However, the subscription takes some time, and the resulting object afterward doesn't have the required data.
let myFunction.pipe(
// mapping to add missing data needed for the front-end
map((results) => ({
...results,
documents: results._embedded.documents.map((document) => ({
...document,
tags: []
})),
})),
// mapping to loop through each document, and use the observable to get the tags with the document id
map((results) => {
let documents = results.documents.map((document: Document) => {
// get Tags for each document
let tagsToReturn = []
this.getDocumentTags(document.id)
.pipe(
// map function to return only the ids for each document, and not the complete tag object
map((tagsArray) => {
const modifiedTagsArray = tagsArray.map((tagObject: any) => {
if (tagObject !== undefined) {
return tagObject.id
}
})
return modifiedTagsArray
})
)
// the subscription to "actually" get the tags
.subscribe((tagsArray: number[]) => {
// Here the tags are found, but the latter code is executed first
// document.tags = tagsArray
tagsToReturn = tagsArray
})
// console.log(JSON.stringify(document))
// Here the tags are not found yet
console.log(JSON.stringify(tagsToReturn))
return { ...document, tags: tagsToReturn }
})
// I then, normally return the new documents with the tags for each document, but it is empty because the subscribe didn't return yet.
return {
_links: results._links,
page: results.page,
documents: documents,
}
}),
map((results) => {
results.documents.forEach((doc) => {
return this.addObservablesToDocument(doc)
})
return results
})
)
I have tried some solutions with switchmap, forkjoin, concat...etc but it didn't work, or i didn't find the correct way to use them. This is why i'm asking if there is a way to stop or another way to handle this problem.
I have tried using different operators like: mergemap, concat, switchmap to swich to the new request, but afterward, i can't have the global object.
I mostly tried to replicate/readapt this in some ways
By using mergemap combined with forkjoin, i was able to replicate what you were looking for.
Not really sure of how i can explain this, because i'm also not an expert coming to Rxjs, but i used the code from : this stackoverflow answer that i adapted
How i understand it is that, when using mergeMap in the pipe flow, you make sur that everything that get returned there, will be executed by the calling "subscribe()",then the mergeMap returns a forkJoin which is an observable for each document tags
I hope this can help
.pipe(
// mapping to add missing data needed for the front-end
map((results) => ({
...results,
documents: results._embedded.documents.map((document) => ({
...document,
tags: []
})),
})),
/******** Added Code *********/
mergeMap((result: ResultsNew<Document>) => {
let allTags = result._embedded.documents.map((document) =>
this.getDocumentTags(document.id).pipe(
map((tagsArray) => tagsArray.map((tagObject: any) => tagObject.id))
)
)
return forkJoin(...allTags).pipe(
map((idDataArray) => {
result._embedded.documents.forEach((eachDocument, index) => {
eachDocument.tags = idDataArray[index]
})
return {
page: result.page,
_links: result._links,
documents: result._embedded.documents,
}
})
)
}),
/******** Added Code *********/
map((results) => {
results.documents.forEach((doc) => {
return this.addObservablesToDocument(doc)
})
return results
})
)
I am trying to verify if the user is inside that list that I capture by axios, the issue is that I have used the FILTER option but it always returns undefined or [], being that if the user exists in that array.
I can't think what else to do, because I validate if it is by console.log() the variable with which I ask and if it brings data.
created() {
this.getStagesDefault()
this.getSalesman()
this.getStagesAmountByUser()
},
methods: {
async getSalesman(){
const { data } = await axios.get('salesman')
this.employees = data.data
},
getStagesAmountByUser(){
console.log(this.user['id'])
var objectUser = this.employees.filter(elem => {
return elem.id === this.user['id']
})
console.log(objectUser)
},
Console
Vue data
The method getSalesman is asynchronous, meaning that getStagesAmountByUser will start executing before getSalesman finishes.
Two ways to fix the problem:
Await the getSalesman method, but you have to make the created method async as well. Change the code as follows:
async created() {
this.getStagesDefault()
await this.getSalesman()
this.getStagesAmountByUser()
}
Attach a .then to the getSalesman function, and start the next one inside the .then. Change the code as follows:
created() {
this.getStagesDefault()
this.getSalesman().then(() => this.getStagesAmountByUser())
}
getSalesman is an async method. At the time of the filter, the array being filtered is still empty.
this.getSalesman() // this runs later
this.getStagesAmountByUser() // this runs right away
Have the methods run sequentially by awaiting the async method:
await this.getSalesman()
this.getStagesAmountByUser()
You can avoid the inefficient clientside filtering if you pass the id to the backend and only select by that id.
Additionally, created only gets called once unless you destroy the component which is also inefficient, so watch when user.id changes then call your method again.
Plus don't forget you must wrap any async code in a try/catch else you will get uncaught errors when a user/salesman is not found etc, you can replace console.error then with something which tells the user the error.
{
data: () => ({
employee: {}
}),
watch: {
'user.id' (v) {
if (v) this.getEmployee()
}
},
created() {
this.getEmployee()
},
methods: {
getEmployee() {
if (typeof this.user.id === 'undefined') return
try {
const {
data
} = await axios.get(`salesman/${this.user.id}`)
this.employee = data.data
} catch (e) {
console.error(e)
}
}
}
}
I have a problem with conditional calls in RxJS.
The scenario is that I have multiple HTTP-Calls inside a forkjoin. But inside the calls there can be dependencies, like I get a boolean back from first call and if that is true the second call should be fired.
This is my code right now:
service.method(parameters).pipe(
tap((data: boolean) => {
foo.bar= data;
}),
concatMap(() =>
service
.method(parameters)
.pipe(
tap((data: KeyValue<number, string>[]) => {
if (true) {
foo.foo = data;
}
})
)
)
)
The problem that I have is that the method now always gets called. My goal is that the method only gets called when the parameter is true, to reduce the amount of calls. I hope somebody can help me.
You can try something like this
service.method(parameters).pipe(
tap((data: boolean) => {
foo.bar= data;
}),
concatMap((data) => data ? // you pass data as parameter and check if true
service // if data is true you return the Observable returned by the service method
.method(parameters)
.pipe(
tap((data: KeyValue<number, string>[]) => {
if (true) {
foo.foo = data;
}
})
) :
of(false) // if data is false you return an Observable containing what you decide it to contain, in this case 'false' but it could be everything
)
)
The idea is that you call the first time the service.method, pass the result of this call to concatMap and in there you decide, based on the parameter passed to concatMap whether to call again service.method and return the Observable it returns or return an Observable that you create within concatMap wrapping whatever value you want.
In my app I am trying to update an array. First I get data from the database and add it to the array and in another method I want to use that array. But the array does not get updated.
If I use my array exerciseList in the DOM it has the data but in the getExercises funciton the length of the array is still 0. It like I run the method before the data is added to the array or something like that.
Any Idea why this is not working?
data: () => ({
exerciseList: []
});
created() {
this.getDataBaseCollection("Exercise", this.exerciseList); // array gets information here
}
mounted() {
this.getExercises();
},
methods: {
getDataBaseCollection: function (CollectionName, list) {
db.collection(CollectionName).onSnapshot(snapshot => {
snapshot.forEach(doc => {
list.push(doc.data());
});
});
},
getExercises: function () {
console.log(this.exerciseList.length); // length is 0 ?? array seems empty
}
},
I think a key missing part may be updating the component's exerciseList variable , not the list argument. They are not the same variable. Objects are passed by reference but arrays are passed to functions by value only which makes list it's own variable independent from excerciseList. This is rough code that shows some ways to make sure exerciseList is updated and how to know when the values are all in the array.
// include exerciseListLoaded to flag when all data is ready
data: () => ({
exerciseList: [],
exerciseListLoaded: false
});
created() {
this.getDataBaseCollection("Exercise"); // array gets information here
}
mounted() {
// based on timing of the `onSnapshot` callback related to `mounted` being called, this may likely still result in 0
console.log("Mounted");
this.getExercises();
},
watch: {
// watch for all data being ready
exerciseListLoaded () {
console.log("All Loaded");
this.getExercises();
}
},
methods: {
// be sure to update the exerciseList on the component
getDataBaseCollection: function (CollectionName) {
// being careful about `this` since within `onSnapshot` I suspect it will change within that function
const componentScope = this;
db.collection(CollectionName).onSnapshot(snapshot => {
snapshot.forEach(doc => {
componentScope.exerciseList.push(doc.data());
// could also still update `list` here as well if needed
});
// setting this allows the component to do something when data is all loaded via the `watch` config
componentScope.exerciseListLoaded = true;
});
},
getExercises: function () {
console.log(this.exerciseList.length); // length is 0 ?? array seems empty
}
},
when you use this inside a function it refers to the function not the vue instance so you may use that may work with you:
getExercises() {
console.log(this.exerciseList.length);
}
I have an array of promises, and I'm trying to push new promises into that array inside of another dispatch.then function, but it appears that the array is always out of scope
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params
let promises = []
versions.forEach((id) => {
// get the version info, and then pass it along
dispatch(ActionCreators.version({ id: id })).bind(promises).then((version) => {
promises.push(dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
})))
})
})
//
promises.push(dispatch(ActionCreators.configuration()))
promises.push(dispatch(ActionCreators.byRef({ language_tag })))
console.log(promises.length)
return Promise.all(promises)
}
},
I've tried a few different approaches, such as setting var that = this right before the dispatch inside of the versions loop, and what is shown here, trying to use .bind(promises) on the dispatch.
promises.length is always 2, (because of the two that are actually getting pushed at the bottom). I can console statements inside of the .then so I know it's getting executed, but the dispatches are not ending up in the promises array.
I could very well be thinking of the dispatch function in an incorrect way.
Any help would be appreciated!
The problem is that since you're adding the promises on then(), you have already returned the array by the time you're adding the promises. So they do get added, but too late.
Instead, try this:
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params;
let promises = [];
versions.forEach((id) => {
// get the version info, and then pass it along
promises.push(dispatch(ActionCreators.version({ id: id })).then((version) => {
return dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
}));
}));
});
//
promises.push(dispatch(ActionCreators.configuration()));
promises.push(dispatch(ActionCreators.byRef({ language_tag })));
console.log(promises.length);
return Promise.all(promises)
}
}