The goal is to filter an array of objects by another array of objects. Each array comes from a different source.
The following setup might look weird, is though for several here unmentioned reasons unfortunately necessary.
Below is my solution so far. Unfortunately I can't make it happen to execute the functions sequentially, so that especially fetchJSONfiles() is completely finished, before filterJSON() is called.
I am stuck here for hours ... any help is highly appreciated and would made my day. Thanks!
Example Data:
allposts: [
{
dateofpost: "1539181118111",
textid: "1",
userid: "Alice",
},
{
dateofpost: "1539181118222",
textid: "3",
userid: "Bob",
},
]
-
allfilteredTexts: [
{
title: "Lorem",
textid: "1",
},
{
title: "Ipsum",
textid: "2",
},
{
title: "Dolor",
textid: "3",
},
]
Expected Outcome:
latestPosts: [
{
title: "Lorem",
textid: "1",
},
{
title: "Dolor",
textid: "3",
},
]
My solution so far:
class Explore extends React.Component {
constructor(props) {
super(props);
this.state = {
allposts: [],
textids: [],
userids: [],
allfilteredTexts: [],
};
}
componentDidMount() {
const allfilteredTexts = {...this.state.allfilteredTexts}
firebase
.firestore()
.collection("allposts")
.orderBy("dateofpost")
.get()
.then(snapshot => {
const allposts = this.state.allposts;
snapshot.forEach(doc => {
allposts.push({
userid: doc.data().userid,
textid: doc.data().textid,
dateofpost: doc.data().dateofpost,
});
});
this.setState({
allposts: allposts,
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
})
.then(() => {
this.filterArrayIds();
})
.then(() => {
this.fetchJSONFiles();
})
.finally(() => {
this.filterJSON();
});
}
filterArrayIds() {
var userids = this.state.userids
var textids = this.state.textids
if (this.state.allposts) {
var filtereduserids = [...new Set([].concat(...this.state.allposts.map(o => o.userid)))];
var filteredtextids = [...new Set([].concat(...this.state.allposts.map(p => p.textid)))];
this.setState({
userids: filtereduserids,
textids: filteredtextids,
})
}
}
fetchJSONFiles() {
if (this.state.userids) {
this.state.userids.forEach((username) => {
var filteredTexts = []
const options = {username} //here would be more API options //
getFile(options)
.then((file) => {
filteredTexts = JSON.parse(file || '[]');
})
.then (() => {
Array.prototype.push.apply(filteredTexts, this.state.allfilteredTexts);
this.setState({
allfilteredTexts: filteredTexts,
})
})
}
}
filterJSON(){
let latestPosts = (this.state.allfilteredTexts.filter(
(el) => { return el.id.indexOf(this.state.textids) !== -1;
}));
}
render () {
return (
<div>
<Switch>
<Route
path='/explore/latest/'
render={(props) => <ExploreLatest {...props} allposts={this.state.allposts} allfilteredTexts={this.state.allfilteredTexts} />}
/>
</Switch>
</div>
)
}
}
export default Explore;
I would suggest modifying like so:
fetchJSONFiles() {
if (this.state.userids) {
return Promise.all(this.state.userids.map((username) => {
var filteredTexts = []
const options = {username} //here would be more API options //
return getFile(options)
.then((file) => {
filteredTexts = JSON.parse(file || '[]');
})
.then (() => {
Array.prototype.push.apply(filteredTexts, this.state.allfilteredTexts);
this.setState({
allfilteredTexts: filteredTexts,
})
}))
}
}
So then the lines:
.then(() => {
this.fetchJSONFiles();
})
Can become:
.then(() => {
return this.fetchJSONFiles();
})
Why?
The reason why fetchJSONFiles
doesn't finish before the rest of the Promise chain is because the Promise chain does not know to wait for the results of fetchJSONFiles
. fetchJSONFiles
makes asynchronous calls, therefore the rest of the synchronous code keeps on executing.
However, by returning a Promise from fetchJSONFiles, we have something to "wait on". This uses the feature Promise.all
, which basically says "create one promise that finishes when every promise in this array of promises finishes".
Instead of forEach
we use map
, because this allows us to create a new array based on the base array, instead of just looping over it. And then instead of just calling getFile
, we return the Promise chain. So we create an array of Promises from this.state.userids
, and create one Promise from that which will resolve when all the fetches are done with Promise.all
.
Then we return that in the initial Promise chain which is located in componentDidMount
. This tells the chain to wait for the result of that Promise (that Promise being the result of this.fetchJSONFiles
) to be complete before continuing, which in this case would involve executing the finally
callback.
Now, there's certain other considerations to...consider. Namely, what happens if there's an error in one of the fetchJSONFiles
call? That's something you'll have to think about, but these changes should just get you up and running to where you want to be.