Duplicate clicks on repeater's button after more than 1 loading of the repeater

I have two repeaters, Each of them with different products (One of them shows the favorite top 5 of the user (repeater1), and the other one the rest of products she likes but are not in top 5(repeater2)). In repeater2 I have a button in each of the containers to move the product to the top 5. So after clicking, the product is eliminated from one dataset, added to another and then the repeaters are reloaded to show the change.

After one reload of the repeater, when I click again in another button to transfer a new product to the top 5, it makes the operation twice, as if clicked twice on the button very fast, then 3, 4 etc… This results in the duplication of code execution and adding the same product several times on the top 5.
I am using the repeaters forEachItem() to set the onclik event since I need to pass to the function some info about the element in which it is being clicked (in this case the favoriteNumber). - You can see some code below-

Is this a bug?? If not, why is this done this way? Is there a workaround to prevent this?

$w("#repeater2").forEachItem( ($item, itemData, index) => {

$item('#image2').src = itemData.favorito1.mainMedia;
$item('#text42').text = itemData.favorito1.name;
$item('#image2').link = itemData.favorito1.productPageUrl;
$item('#image2').target = "_blank";

$item('#moverAFavoritos').onClick(transferToTopFive(itemData.favoriteNumber, 5));
});

You don’t indicate what’s in the function. How are you transferring an item from one dataset to another? Do both datasets connect to the same collection?

Please provide more information regarding what you are doing.

Hi Yisrael,
Sorry for the late reply,
I did some changes and now I’m using only one collection. However the duplication still happens on the repeaters. Let me try to give you more information about it.
This is how the website looks like:


I have repeater1 where the top 5 appears, and repeater 2 where the rest of favorites appear (6,7,8…) and where you can see the buttons that move a product to favorite number 5 → moving it to top 5.
Both repeaters use the same collection
This is the collection where the favorites are stored:

When you click on the button that reads “subir al nº 5” of one of the products in repeater2, it changes the product’s favoriteNumber to 5 and increases by 1 all the products in between the one clicked and number 5.

I leave you the code below with the functions where the 2 repeaters are loaded with all the data, and the function to transfer the favorite to number 5.

async function loadFavoritesList () {


let favoritesList = await wixData.query("Productosfavoritos")
.eq("userId", user.id)
.include('favorito1')
.le("favoriteNumber", 5)
.ascending("favoriteNumber")
.find()

console.log(favoritesList.length);

if(favoritesList.length < 5){
$w('#repeater1').collapse();
$w('#box3').show();
$w('#box3').expand();
$w('#groupReservas').collapse();
}else{

$w('#repeater1').show();
$w('#repeater1').expand();
$w('#repeater1').data = favoritesList.items;
$w('#repeater1').onItemReady(myItemReady);

$w('#repeater1').forEachItem(($item, itemData, index) => {
$item('#loading').collapse();
$item('#delete').onClick(removeItem(itemData._id, itemData.favoriteNumber));
});


}
}
async function loadReserveList () {

let reserveList = await wixData.query("Productosfavoritos")
.eq("userId", user.id)
.include('favorito1')
.gt("favoriteNumber", 5)
.ascending("favoriteNumber")
.find()

$w('#text43').show();
if(reserveList.length > 0){
$w('#repeater2').show();
$w('#repeater2').expand();
//$w('#box1').collapse();
$w('#repeater2').data = reserveList.items;
//$w('#repeater2').onItemReady(myReserveItemReady);

$w("#repeater2").forEachItem( ($item, itemData, index) => {

$item('#loadingReserve').collapse();
$item('#image2').src = itemData.favorito1.mainMedia;
$item('#text42').text = itemData.favorito1.name;
$item('#image2').link = itemData.favorito1.productPageUrl;
$item('#image2').target = "_blank";

$item('#removeItem').onClick(removeItem(itemData._id, itemData.favoriteNumber));
$item('#moverAFavoritos').onClick(transferToTopFive(itemData.favoriteNumber, 5));
});

}
else{
$w('#repeater2').collapse();
$w('#box1').show();
$w('#box1').expand();
}
}


function myReserveItemReady($w, reserveListItem){

let product = reserveListItem.favorito1;
$w('#image2').src = product.mainMedia;
$w('#text42').text = product.name;
$w('#image2').link = product.productPageUrl;
$w('#image2').target = "_blank";

}


function myItemReady($w, favoritesListItem){

let product = favoritesListItem.favorito1;
let favoriteNumber = favoritesListItem.favoriteNumber;
$w('#image1').src = product.mainMedia;
$w('#productName').text = product.name;
$w('#dropdown1').value = favoriteNumber;
$w('#image1').link = product.productPageUrl;
$w('#image1').target = "_blank";
$w('#text85').hide();
//$w('#delete').onClick(removeItem(favoritesListItem._id, favoritesListItem.favoriteNumber));
}


function transferToTopFive (oldValue, newValue){
return async function(){
console.log("in"); //this is how I see the call is being repeated
$w('#repeater1').forEachItem(($item, itemData, index) => {
$item('#loading').expand();
});
$w('#repeater2').forEachItem(($item, itemData, index) => {
$item('#loadingReserve').expand();
});
let favoritesToChange = await wixData.query("Productosfavoritos")
.eq("userId", user.id)
.between("favoriteNumber", newValue, oldValue + 1)
.ascending("favoriteNumber")
.find();
favoritesToChange.items[favoritesToChange.items.length-1].favoriteNumber = newValue;
await wixData.update("Productosfavoritos", favoritesToChange.items[favoritesToChange.items.length-1]);
for (var j = 0; j < favoritesToChange.length - 1; j++) {
favoritesToChange.items[j].favoriteNumber += 1;
await wixData.update("Productosfavoritos", favoritesToChange.items[j])
.then((results) =>{
let item = results;
})
.catch((err) => {
console.log(err);
});

}

loadFavoritesList();
loadReserveList();

}
}

So the main problem is that after the data of the repeaters is reloaded, when clicking on the button of one of the products, the function TransferToTop5 is executed several times. I use the console to show you this:

If I reload the page, it works well again. So it is as if everytime that a repeater is reloaded, when clicking on a button inside one of its components it repeats the action several times. As if there were several buttons one of top of the other

Hope this serves and you can help me with it.

Thanks in advance

Hi Enrique - did you manage to solve this? I have exactly the same problem.

I had a similar issue, but it isn’t too difficult to resolve.
The reason that you get multiple click instances is because in a repeater, it cycles through all the items in the repeater.

If you have 8 items in the repeater and click on the first one, it will log 8 instances of the event. If you click on the second-to-last item, it will log 2 instances; etc.

I was sending the data into an array so I used the Set() javascript function. If you need a single result, then you should pass the click-values into an array and then create a new set for your code to use.

Edited to add: if your click event triggers an immediate action, then you don’t need to worry about how many times it reloads the exact same data

let chars = ['A', 'B', 'A', 'C', 'B'];
let uniqueChars = [...new Set(chars)];

console.log(uniqueChars);


//returns [ 'A', 'B', 'C' ]

Hi Gladen - thanks very much for your answer.

I had hoped that as the button click event is in a “forEachItem” it would only run on that item. In a way it does… My code is extracting an item from an array and adding in a new one, so that the user can cycle through three options for that item. The multiple “phantom” button clicks that follow the first one execute this code multiple times, but they only add the same item again (and again, and again…) they don’t add the options that are set up for each of the other items.

I can’t see when it would be a good idea for a button inside an item to behave that way…

Another oddity is that if I only press the button on one item, after the repeater loads, there is no duplication, no matter how many times I press that particular button and no matter how many items are in the repeater. Only when I press a button in a different item does the duplication start. But then the more items I press the button in, the duplication seems to become exponential and it crashes the page.

My work-around fix has been to add a button.disable() in the first line of the code after the click. Although I get a couple of “button.disable is not a function” warnings - (maybe because it is already disabled by the time the second and third attempts occur) - it seems to work and stops the code running multiple times. Then the new array is used to set the repeater items and the phantom button presses end.

My worry about just letting the code run and then extracting the new item from the array is that it seemed to be taking a very long time to cycle - because of the exponential effect.

However, if I run into any more problems I will definitely try your idea - so thanks again.

I am pretty new to this forum, so I don’t know if a Wix person will dive in to offer a better solution or to tell me why mine doesn’t work - but I hope it helps with a work-around.

Hi, philip.mudd !

In my case, I put a boolean manager to permit the function called inside the $item(button).onClick() to run once and just after 5 seconds it can be executed again. Check it out:

let actionClickInterval;
let actionClickActive = true;

$w.onReady(function () {
    $w("#repeater1").onItemReady(($item, itemData, index) => {
        $item('#myButton').onClick((event) => {
            myFunction(itemData);
        });
    });
});

function myFunction(item) {

    if (actionClickActive) {
        actionClickActive = false;
        actionClickInterval = setInterval(() => {
            actionClickActive = true;
            clearInterval(actionClickInterval)}, 5000);

        //RUN THE FUNCTION'S CODE HERE
    }
}

Hope it helps someone.

Thanks Plinio

In the end I solved it with help from Katerina at Wix, by moving the button’s onClick event out of the forEachItem loop and using event.context to identify which item’s button has been pressed. This has helped with a few other issues I has been having.

Hi, Philip!

Thank you too for this reply. This solution definitely is much better! I’ll try this soon.

Regards.

@philipmudd could you share the correct code?

@philipmudd could you share the correct code?