1

I have some items in firestore.I want to show them sequentially but in random serial. So, I did the following code. Here I want to show a specific question and then when "next" button is clicked I want to show the next item. Here, in the forEach loop, I am rendering items to the renderQuestion function. And they are showing them in frontend using HTML and CSS.

But I am fetching this issue, where they keep showing everything till the end of the loop. They don't wait for the "next" button to be pressed. So, how can I fix this? I am new to JS, please forgive me, if I am already doing something wrong.

renderQuestion = (qID, roomID) => {
  q.innerHTML = qID.data().question;
  ans1.innerHTML = qID.data().a;
  ans2.innerHTML = qID.data().b;
  ans3.innerHTML = qID.data().c;
  ans4.innerHTML = qID.data().d;
  /* next.addEventListener("click", (e) => { //not working
    e.preventDefault();
    console.log("done");
  }); */
};

runningExam = async (review) => {
  const totalQ = review.data().total_questions;
  let arr = [];
  for (let i = 0; i <= totalQ; i++) {
    arr.push(false);
  }
  let n = totalQ;

  while (n) {
    let randomNumber = getRandom(totalQ);
    if (!arr[randomNumber]) {
      arr[randomNumber] = true;
      await db
        .collection("examrooms")
        .doc(review.id)
        .collection("questions")
        .where("serial", "==", randomNumber)
        .get()
        .then((snapshot) => {
          snapshot.docs.forEach((doc) => {
            renderQuestion(doc, review);
          });
        });
      n--;
    }
  }
};
mahin
  • 57
  • 6

1 Answers1

1

It looks like you're trying to block the runningExam function until the user presses a button. I found another thread to achieve this.

Basically, turn the renderQuestion function into an asynchronous one, and wait for user input everytime it's called.

renderQuestion = async (qID, roomID) => {
  const timeout = async ms => new Promise(res => setTimeout(res, ms));
  let userClicked = false;

  q.innerHTML = qID.data().question;
  ans1.innerHTML = qID.data().a;
  ans2.innerHTML = qID.data().b;
  ans3.innerHTML = qID.data().c;
  ans4.innerHTML = qID.data().d;
  next.addEventListener("click", (e) => {
    userClicked = true;
    e.preventDefault();
    console.log("done");
  });
  
  while (userClicked === false) await timeout(50);
};

and in runningExam:

then( async (snapshot) => {
      for(let doc of snapshot.docs) {
        await renderQuestion(doc, review);
      };
  });

There's probably better ways to program this task, this is just a possible method to do it the way you were trying to.

byter11
  • 66
  • 3
  • Unfortunately, this isn't working. It keeps showing all the items and doesn't wait for the "next" button just like before :( – mahin May 30 '21 at 15:23
  • 1
    My bad, that was because forEach doesn't work well with async/await. I updated the `runningExam` function to fix that. – byter11 May 30 '21 at 15:54
  • Thanks, it worked. But, I am curious to know doesn't await work with forEach? – mahin May 30 '21 at 16:24
  • 1
    [Here](https://stackoverflow.com/a/54532600/16055511)'s an explanation with source to the actual implementation of forEach. I don't fully understand it yet either, just learned this today :) – byter11 May 30 '21 at 16:36