Search code examples
javascriptvuejs3settimeoutvue-composition-apicleartimeout

Vue 3 not able to clearTimeOut


I'm learning Vue 3 doing a memory game. I have an proxy array of objects(cards), objects have this format:

{
    "id": "01",
    "image": "01.jpg",
    "isFlipped": false
}
{
    "id": "07",
    "image": "01.jpg",
    "isFlipped": false
}

My flipCard méthodes turns card.isFlipped:true to reveal the image. When 2 cards are revealed thé compare méthode is called. If there is match cards dont unflip. If there is no match cards unflip again after setTimeOut of 1.5 seconds. If another cars is flipped before the 1.5 seconds of setTimeOut I need to clear this timeOut to unflip the 2 revealed cards before unflipping the next one (before the 1.5 secs).I've been trying to achieve this for 2 months. Could anybody help me out please , thanks

My component

// flip 2 cards and compare them
function flipCard(card) {
  clearTimeout(timeoutId.value);
  card.isFlipped = true;
  clicksNumber.value += 1;
  comparedCards.value.push(card);

  if (comparedCards.value.length === 2) {
    compare(comparedCards.value[0], comparedCards.value[1]);
  }
}

let timeoutId = ref();
function unflip(card) {
  // Set a timeout and assign the ID to the timeoutId variable
  timeoutId.value = setTimeout(() => {
    card.isFlipped = false;
  }, 1500);
}
// compares 2 flipped cards
function compare(a, b) {
  if (a.image == b.image) {
    found.value.push(a, b);
    // remove found values from not found array
    notFound.value = notFound.value.filter(
      (item) => !found.value.includes(item)
    );

  } else {
    comparedCards.value.forEach((card) => {
      unflip(card); //unflips cards agter 1.5 secs
    });
  }
  comparedCards.value = [];
}

I tried initializing the total clicksNumber and the click number each time we have 2 cards to compare (stopClick )

 let clicksNumber = ref(0);
let stopClick = ref();

and then when reaching 2 cards to compare get the click n° when this happened and watch for it comparing to totalClicks If these are different call clearTimeout(timeoutId)

function flipCard(card) {
  clearTimeout(timeoutId.value);
  card.isFlipped = true;
  clicksNumber.value += 1;
  comparedCards.value.push(card);

  if (comparedCards.value.length === 2) {
    stopClick.value = clicksNumber.value;
    compare(comparedCards.value[0], comparedCards.value[1]);
  }
}

watch(clicksNumber, (stopClick) => {
  if (clicksNumber.value != stopClick) {
clearTimeout(timeoutId)
  }
});

Also tried about 20 different things that didnt work... :-(

I dont have any errors, just when clicking before 1.5 seconds I can have 3 4 or 5 cards flipped at the same time


Solution

  • Since comparedCards contains two items, you set two timeouts here:

        comparedCards.value.forEach((card) => {
          unflip(card);
        });
    

    So timeoutId will contain only the last timeout id.

    To fix this, you can either make timeoutId an array, or change unflip() to handle two cards:

    function unflip(cards){
      timeoutId.value = setTimeout(() => {
        cards.forEach(card => card.isFlipped = false);
      }, 1500);
    }
    

    Btw. I don't think making timeoutId a ref does much for you - you will never use it in the template.


    The second issue is that you don't actually just want to cancel the timeout. If two non-matching cards are revealed and the player flips another card, you do not just want to cancel waiting to turn the two previous cards again, but you want to unflip them right away:

    let timeoutId = null; // <--- doesn't have to be a ref
    
    function compare(a, b) {
      if (a.image == b.image) {
        ...
        comparedCards.value = []; // <--- cards match, remove right away
      } else {
        timeoutId = setTimeout(unflip, 1500); // <--- wait to unflip
      }
      // <--- do not clear compared cards here
    }
    
    
    function flipCard(card) {
      if(comparedCards.value.length >= 2){ // <-- cannot have more than two cards open at the same time
        unflip() //<--- abort timeout, unflip right away 
      }
      if (card.isFlipped) { //<--- prevent flipping the same card twice
        return
      }
      card.isFlipped = true;
      comparedCards.value.push(card);
    
      if (comparedCards.value.length === 2) {
        compare(...comparedCards.value);
      }
    }
    
    function unflip() {
      if(timeoutId){
        clearTimeout(timeoutId);
        timeoutId = null;
      }
      comparedCards.value.forEach(card => card.isFlipped = false); // unflip all opened cards
      comparedCards.value = [] // no more open cards
    }
    

    I would probably rename comparedCards to unflippedCards or something.