onClick triggering twice

I have this weird issue that after my database is filtered, the increment and decrement buttons runs twice when clicked once. Here’s the part of the code that’s running twice (it’s inside a repeater):

$w("#productsRepeater").onItemReady( ($item, itemData) => {
...
$item("#incrementQuantity").onClick (() => {
            if (Number($item("#quantity").value) < itemData.quantityInStock ) {             
                $item("#quantity").value = String(parseInt($item("#quantity").value)+1);
                $item("#decrementQuantity").enable();
            }
        })

        $item("#decrementQuantity").onClick (() => {
            if (Number($item("#quantity").value) > 1 ) {
                $item("#quantity").value = String(parseInt($item("#quantity").value)-1);
            }
            if (Number($item("#quantity").value) === 1) {
                $item("#decrementQuantity").disable();
            }
        })

website:
quinacozinha
dot com
dot br
slash loja-teste

Could someone point me to my mistake?

I can provide the full code if necessary.

Sincerely,
Lucas.

What happens that makes you think that the code is run twice? Do you see the results of the console.log() statements twice when clicking each button? Or does the amount just increment/decrement by two?

Hi, Yisrael. I see the results, it adds 1 then 1 again very fast. So it’s like adding 2…

@lucaszanotelli This doesn’t happen for me. I click on + and it adds 1, and I click on - and it subtracts 1. Works as expected.

@yisrael-wix It happens only after database is filtered with this query:

after this function is called, onClick starts acting strange, adding by 2.

obs: Sorry about the image but otherwise Wix wouldn’t allow me to publish my reply because it was talking a(n) (inexistent) link.

@yisrael-wix

That’s a lot of code to have to deal with. Understand that we are unable to debug or rewrite complex code and page configurations. I would recommend adding console.log() statements to aid in understanding what’s happening in your code.

One thing that I noticed is that you are incorrectly using await together with .then() when calling filterWithCode.search() in the frontend code. This will result in incorrect results. I’m not sure if that’s the cause of your problem, but it’s definitely a problem.

I took a closer look and I believe that the problem is that each time that filtered results are set to the Repeater (to be displayed), additional onClick() event handlers are created. This can be solved by not setting the onClick() event handlers in the onItemReady() function. Instead, set them up as standalone functions.

Something like this:

$w.onReady( function () {

    $w("#productsRepeater").onItemReady( ($item, itemData, index) => {
    ... your code here
    });

    $w("#decrementQuantity").onClick( (event) => {
        let $item = $w.at(event.context);
        if (Number($item("#quantity").value) > 1 ) {
            $item("#quantity").value = String(parseInt($item("#quantity").value)-1);
            console.log($item("#quantity").value);
        }
        if (Number($item("#quantity").value) === 1) {
            $item("#decrementQuantity").disable();
        }
    } );

   ... other code
} );

Yes, that works (partially)!

There’s another problem now: how can I access the productInStock information that I was accessing via itemData ?

@lucaszanotelli

I do not understand why you have such problems when placing your onClick()-function inside → onItemReady().

I use it ALWAYS without having any issues…

$w('#yourRepeaterIdhere').onItemReady(($item, itemData, index)=>{
      console.log("Item-Data-I: ", itemData);
      
      $item('#yourButtonID').onClick((event)=>{console.log(event.target)
           console.log("Item-Data-II: ", itemData);
      });
});

Item-Data-I: → should list all items
Item-Data-II: —> should give back just the selected item-data.

Test it and also take a look at the third implemented console-log.

The whole code for frontend-version would look like the following…

EXAMPLE: This example works with Stores/Products…

import wixData from 'wix-data';

$w.onReady(async function() {
  // First getting the needed data out of Stores-DB..... ---> "productData"
  let productData = await get_AllProductsData(); 
  console.log("Product-Data: ", productData);
  
  //Now you are ready to feed your REPEATER with the gotten DATA....
  $w('#yourRepeaterIDhere').data = productData;  
});
    
  
  // Get all PRODUCTS-DATA------------------------------------------------------------
async function get_AllProductsData() {
//    const options = {
//        "suppressAuth": true,
//        appOptions: {
//            "includeVariants": true,
//            "includeHiddenProducts": true
//        }
//    }
    return wixData.query("Stores/Products")
    //.skip(50)
    .limit(100)
    .find()//options
    .then(async(res)=>{//console.log("RES-PRODUCTS: ", res);
        if (res.items.length > 0) {console.log("Data found!");
            let items = res.items; //console.log("PRODUCTS-ITEMS: ", items);
            return (items);
        }
        else {console.log("No data found!"); return ([]);}
    }).catch((err)=>{console.log(err); return [];});
}

The same one more time, but this time fired by a button-Click…

import wixData from 'wix-data';

$w.onReady(async function() {
  // First getting the needed data out of Stores-DB..... ---> "productData"
  $w('#myStartButtonIDhere').onClick(async()=>{
      let productData = await get_AllProductsData(); 
      console.log("Product-Data: ", productData);
      //Now you are ready to feed your REPEATER with the gotten DATA....
      $w('#yourRepeaterIDhere').data = productData;  
  });  
});
    
  
  // Get all PRODUCTS-DATA------------------------------------------------------------
async function get_AllProductsData() {
//    const options = {
//        "suppressAuth": true,
//        appOptions: {
//            "includeVariants": true,
//            "includeHiddenProducts": true
//        }
//    }
    return wixData.query("Stores/Products")
    //.skip(50)
    .limit(100)
    .find()//options
    .then(async(res)=>{//console.log("RES-PRODUCTS: ", res);
        if (res.items.length > 0) {console.log("Data found!");
            let items = res.items; //console.log("PRODUCTS-ITEMS: ", items);
            return (items);
        }
        else {console.log("No data found!"); return ([]);}
    }).catch((err)=>{console.log(err); return [];});
}

The same again, but this time our BUTTON is inside another repeater…

import wixData from 'wix-data';

$w.onReady(async function() {
  // First getting the needed data out of Stores-DB..... ---> "productData"
  
  $w('#yourRepeaterIdhere').onItemReady(($item, itemData, index)=>{
      console.log("Item-Data-I: ", itemData);
      
      $item('#yourButtonID').onClick(async(event)=>{console.log(event.target)
           console.log("Item-Data-II: ", itemData);
           let productData = await get_AllProductsData(); 
           console.log("Product-Data: ", productData);
           //Now you are ready to feed your REPEATER with the gotten DATA....
           $w('#anotherRepeaterIDhere').data = productData; 
      });
  });  
});
    
  
  // Get all PRODUCTS-DATA------------------------------------------------------------
async function get_AllProductsData() {
//    const options = {
//        "suppressAuth": true,
//        appOptions: {
//            "includeVariants": true,
//            "includeHiddenProducts": true
//        }
//    }
    return wixData.query("Stores/Products")
    //.skip(50)
    .limit(100)
    .find()//options
    .then(async(res)=>{//console.log("RES-PRODUCTS: ", res);
        if (res.items.length > 0) {console.log("Data found!");
            let items = res.items; //console.log("PRODUCTS-ITEMS: ", items);
            return (items);
        }
        else {console.log("No data found!"); return ([]);}
    }).catch((err)=>{console.log(err); return [];});
}

In your case, since you are working on backend, just export your query-function to backend, like you already did.

And by the way, the following post also could be interessting for you…

@russian-dima Thank you but it didn’t work here. For now the only solution to not triggering onClick() twice is to put it outside onItemReady() .

Unfortunatelly it’s not a full solution since I still need the product data inside that function. I’m trying to access it from event.target.parent but no success yet.

Any idea on how to access the product specific data?

@lucaszanotelli To get the data…

$w.onReady( function () {
  $w("#repeatedContainer").onClick( (event) => {
    let $item = $w.at(event.context);
    let clickedItemData = $item("#myDataset").getCurrentItem();
  } );
} );

See Retrieve Repeater Item Data When Clicked.

@lucaszanotelli
Strange! Just did some test by my own to be sure that it works…
and it works, like expected… → NO DOUBLED - TRIGGERING .


You get exactly the item you need onClick-action of a button.
All console-logs as expected…

For DATA-I you get the whole bunch of items…

Tested code…

import wixData from 'wix-data';

$w.onReady(async function() {
  // First getting the needed data out of Stores-DB..... ---> "productData"
  
  let productData = await get_AllProductsData();  console.log("Product-Data: ", productData);

  $w('#repProduct').data = productData;
  
  $w('#repProduct').onItemReady(($item, itemData, index)=>{console.log("Item-Data-I: ", itemData);  

        $item('#button16').onClick(async(event)=>{console.log("Event-Target:", event.target)           
           console.log("Item-Data-II: ", itemData);
           $item('#button16').label = itemData.name
           //Now you are ready to feed your REPEATER with the gotten DATA....
           //$w('#anotherRepeaterIDhere').data = productData; 
        });
    });  
});
    
  
  // Get all PRODUCTS-DATA------------------------------------------------------------
async function get_AllProductsData() {
//    const options = {
//        "suppressAuth": true,
//        appOptions: {
//            "includeVariants": true,
//            "includeHiddenProducts": true
//        }
//    }
    return wixData.query("Stores/Products")
    //.skip(50)
    .limit(100)
    .find()//options
    .then(async(res)=>{//console.log("RES-PRODUCTS: ", res);
        if (res.items.length > 0) {console.log("Data found!");
            let items = res.items; //console.log("PRODUCTS-ITEMS: ", items);
            return (items);
        }
        else {console.log("No data found!"); return ([]);}
    }).catch((err)=>{console.log(err); return [];});
}

@russian-dima It’s puzzling. I’ve never had a problem, but a team member recently complained about it, and I’ve heard others complain.

I’ve referred this to QA for evaluation.

@russian-dima Did you try repopulating the Repeater? The problem occurs when setting Repeater.data = ; then clearing Repeater.data = []; and then setting again Repeater.data = ; After setting the second time, onClick() functions (and other event handlers, [sometimes?] run twice. Clear and set again and the event handlers then run three times.

@yisrael-wix
Yes, i also have had these problems in the past, the same like decribed in this post.
But one day i changed something in my code-structure and i did not pay attention on it → RESULT → The ERROR was gone.

And yes →

$w.onReady(function(){$w("#repeatedContainer").onClick((event)=>{let $item = $w.at(event.context);let clickedItemData = $item("#myDataset").getCurrentItem();});});

This one is perhaps the better version of how to solve it and i also used it some time ago, but onClick inside onItemReady works for me. And there is normaly no need to use Yisraels provided version (exept if you have issues :sweat_smile:).

Did you try repopulating the Repeater? The problem occurs when setting Repeater.data = ; then clearing Repeater.data = ; and then setting again Repeater.data = ; After setting the second time, onClick() functions (and other event handlers, [sometimes?] run twice. Clear and set again and the event handlers then run three times.

Yes, that is correct. I could observe the same behaviour once, like described
For example this one do ERRORs…

import wixData from 'wix-data';

$w.onReady(async function() {
  // First getting the needed data out of Stores-DB..... ---> "productData"
  
  let productData = await get_AllProductsData();  console.log("Product-Data: ", productData);

  $w('#repProduct').data = productData;
  
  $w('#repProduct').onItemReady(($item, itemData, index)=>{console.log("Item-Data-I: ", itemData);  

        $item('#button16').onClick(async(event)=>{console.log("Event-Target:", event.target)           
           console.log("Item-Data-II: ", itemData);
           $item('#button16').label = itemData.name
           //Now you are ready to feed your REPEATER with the gotten DATA....
           //$w('#anotherRepeaterIDhere').data = productData;            
        });

        $item('#button20').onClick(async(event)=>{
            let collectionData = await get_AllCollectionsData(); console.log("Collection-Data: ", collectionData); 
            $w('#repProduct').data = [];
            $w('#repProduct').data = productData;
        });
    });  
});
    
  
  // Get all PRODUCTS-DATA------------------------------------------------------------
async function get_AllProductsData() {
//    const options = {
//        "suppressAuth": true,
//        appOptions: {
//            "includeVariants": true,
//            "includeHiddenProducts": true
//        }
//    }
    return wixData.query("Stores/Products")
    //.skip(50)
    .limit(100)
    .find()//options
    .then(async(res)=>{//console.log("RES-PRODUCTS: ", res);
        if (res.items.length > 0) {console.log("Data found!");
            let items = res.items; //console.log("PRODUCTS-ITEMS: ", items);
            return (items);
        }
        else {console.log("No data found!"); return ([]);}
    }).catch((err)=>{console.log(err); return [];});
}


// Get all COLLECTIONS-DATA---------------------------------------------------------------------------------------
async function get_AllCollectionsData() {
    return wixData.query("Stores/Collections")
    .limit(100)
    //.skip(50)
    .find()
    .then((res)=>{//console.log("RES-COLLECTIONS: ", res);
        if (res.items.length > 0) {console.log("Collection-Data found!");
            let items = res.items; //console.log("COLLECTIONS-ITEMS: ", items);
            return (items);
        }
        else {
            console.log("No Collection-Data found!");            
            return ([]);
        }
    }).catch((err)=>{console.log(err); return [];});
}
// Get all PRODUCTS-DATA-------------------------------------------------------------------------------------------

I think i was able to reconstruct the ISSUED-SITUATION, seems like Yisrael was right.

DOUBLED-RESULTS after REPOPULATION…(one click onto button16)

@lucaszanotelli
I can’t say, that this is a good end-solution.

Like the post-opener already asked twice…

Yes, that works (partially)!

There’s another problem now: how can I access the productInStock information that I was accessing via itemData ?
It is not possible anymore to get the → itemData <–. the direct way.
Instead you have to grab the ID which you get as RESULT and do a FURTHER QUERY to find the item which was clicked, as i could see after some time of intensive investigation/analysing the code.

The offered version is (out of my point of view) → just a → BAD WORKAROUND (sorry but i have to say it).

Instead of providing such a bad workaround in the API-DOCs, the GLITCH/BUG/BAD OnItemReady-sideeffect, should be fixed/eleminated by Wix.
So, in future the onClick-handler also should be able to be used inside of the onItemReady-code-part, without causing bad side-effects.

Or do you not agree with me Yisrael?

And this ERROR is already existent for a long time.

@russian-dima Hmmm, looks like I need to do some “intensive investigation/analysing” of my own.

@russian-dima Oh, and by the way, I’ve sent this issue on to QA. I want to know what they say. I agree that this is not the expected, or desirable, behavior, but there might be some nasty underlying technical issues.