0

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.

  • Step one is to get from a Firebase database an array of objects with metadata about some (the latest) uploaded posts (allposts).
  • In step two this array will be filtered for userids and textids (filterArrayIds).
  • In step three for each filtered userid an API will be called to get detail informations to all existing posts of the corresponding userid. These will be merged into one array of posts (fetchJSONFiles).
  • In step four this merged array of posts should be filtered for all the textids in the array of textids of step two (filterJSON).

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;

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks so so much! Especially for the explanation. I have to read more about the "Promise" functionality... (wishing though it would not be necessary... .then (() => kind of feels pretty intuitive ... not sure why it is not simply waiting till the function that is called is finished ;) ... but hey, that's probably just me. ;)
0
.catch(function(error) {
    console.log("Error getting documents: ", error);
  })
  .then(() => {
          this.filterArrayIds();
  })
  .then(() => {
          this.fetchJSONFiles();
  })
  .finally(() => {
          this.filterJSON();
  });

The first .then() is called when the first promise resolves, so are the second, third and .finally() (finally is called weither your promiser resolve or not). They all watch for the first promise to resolve.

What you need to do is as following:

firebase
  .get()
  .then(snapshot => {
    // stuff happening with snapshot
    this.filterArrayIds().then(() => {
      this.fetchJSONFiles().then(() => {
        this.filterJSON();
      });
    });
  });

Your methods filterArraysId(), fetchJSONFiles() will have to return a Promise which resolves when they are done working ;)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.