javascriptnode.jsbuttondiscord.jsinteraction

Discord.js: bot is unable to handle multiple commands with buttons correctly


I was creating a new help command for my discord bot when I encountered this problem: When two or more Help Commands are called, only the last one would handle the button interactions correctly, and all the others would mess up.

My help command is divided in sub-menus that users can select with a StringSelectMenu. Each sub-menu contains page(s) of commands of a same category.

Example

When I call the /help command twice and go back interacting with the buttons of the first command, if I reach the last page of the sub-menu, the "Next" and "Last" buttons should be disabled, but they are not in this case. However, the last command still works as it should.

This issue also happens when I navigate back to the first page in the previous commands.

Video demo of my problem:

https://www.youtube.com/watch?v=aWtvcNmrX80

What I think is causing this issue:

I think the variable currentPage is static and shared between the commands.

However, when a second command is called, only this second command could update the value of currentPage, and since there is only a single currentPage shared between all the commands, the previous commands would be forced to use the value of currentPage of the last command, which modifies the page index of the previous commands to unwanted ones.

What I expect to happen:

What I have tried:

My current code:

// Handling received interactions from the collector
            collector.on("collect", async (i) => {
                if (i.user.id == interaction.user.id) {
                    // If the interaction received is not from the buttons nor from the select menu, do nothing.
                    if (!i.isButton() && !i.isStringSelectMenu()) return;
                    // Defer the reply since the command takes longer time than usual to complete
                    await i.deferUpdate();
                    // Button handling starts here
                    if (i.isButton()) {
                        // If the user is still on the starting menu, disable all the buttons
                        if (currentCategory == menu.init) {
                            for (const i of buttons) {
                                await i.setDisabled(true);
                            }
                            // User is not on the starting menu
                        } else {
                            // Update the page according to the interaction of the user
                            switch (i.customId) {
                                case "previous":
                                    currentPage -= 1;
                                    break;
                                case "next":
                                    currentPage += 1;
                                    break;
                                case "first":
                                    currentPage = 0;
                                    break;
                                case "last":
                                    currentPage = currentCategory.length - 1;
                                    break;
                                default:
                                    handleError(
                                        i,
                                        "Unknown button interaction."
                                    );
                            }

                            // Disabled and enable certain buttons to avoid errors
                            // First page
                            if (currentPage == 0) {
                                await previousBtn.setDisabled(true);
                                await firstBtn.setDisabled(true);
                                // Re-enable in case disabled
                                await nextBtn.setDisabled(false);
                                await lastBtn.setDisabled(false);
                                // Last page
                            } else if (
                                currentPage ==
                                currentCategory.length - 1
                            ) {
                                await nextBtn.setDisabled(true);
                                await lastBtn.setDisabled(true);
                                // Re-enable in case disabled
                                await previousBtn.setDisabled(false);
                                await firstBtn.setDisabled(false);
                                // Middle pages
                            } else {
                                // Reenable all buttons in case disabled
                                for (const button of buttons) {
                                    await button.setDisabled(false);
                                }
                            }

                            console.log("BTN:", currentPage);

                            // Update the embed message;
                            await i.editReply({
                                embeds: [currentCategory[currentPage]],
                                components: [selectMenuRow, buttonsRow],
                            });
                        }
                        // Select Menu Handling
                    } else if (i.isStringSelectMenu()) {
                        // i.values[0] is the command category that the user had chosen
                        switch (i.values[0]) {
                            case "fun":
                                currentCategory = menu.fun;
                                break;
                            case "utils":
                                currentCategory = menu.utilities;
                                break;
                            case "welcomebye":
                                currentCategory = menu.welcome_goodbye;
                                break;
                            default:
                                currentCategory = menu.init;
                                break;
                        }
                        currentPage = 0; // Reset the page number
                        // Disable the previous and next buttons to avoid unexpected interactions
                        await firstBtn.setDisabled(true);
                        await previousBtn.setDisabled(true);
                        // If there are more than one page of help in that category, allow the user to move on the next pages
                        if (currentCategory.length > 1) {
                            await nextBtn.setDisabled(false);
                            await lastBtn.setDisabled(false);
                            // If there is only one page of help, disable all the buttons to prevent causing errors
                        } else {
                            await nextBtn.setDisabled(true);
                            await lastBtn.setDisabled(true);
                        }
                        await interaction.editReply({
                            embeds: [currentCategory[currentPage]],
                            components: [selectMenuRow, buttonsRow],
                        });
                    }
                    // A non-command-caller user tries to interact with the command
                } else {
                    await i.deferUpdate();
                    await i.followUp({
                        content: `This is not for you.`,
                        ephemeral: true,
                    });
                }
            });

Any help would be appreciated! Thank you!


Solution

  • I found an alternative method to handle my button interactions thanks to the help of fellow programmer. Here's the solution:

    I created a function that will use the two arguments currentPage and maxPage to update and return new button components. Every time I need to edit the button components, I could just pass this function in the components argument in interaction.editReply().

    Conditions that will return boolean values are passed in the setDisabled function of each ButtonBuilder() so the buttons could disable themselves when condition is met.

    Here's the function:

    // A function used to update the buttons function
    getButtons(currentPage, maxPage) {
        const components = new ActionRowBuilder().addComponents(
            new ButtonBuilder()
                .setCustomId("first")
                .setLabel("First Page")
                .setStyle(ButtonStyle.Primary)
                .setDisabled(!(currentPage > 0)), // Disable if user on first page
            new ButtonBuilder()
                .setCustomId("previous")
                .setLabel("⬅️")
                .setStyle(ButtonStyle.Primary)
                .setDisabled(!(currentPage > 0)), // Disable if user on first page
            new ButtonBuilder()
                .setCustomId("next")
                .setLabel("➡️")
                .setStyle(ButtonStyle.Primary)
                .setDisabled(currentPage == maxPage), // Disable if user on last page
            new ButtonBuilder()
                .setCustomId("last")
                .setLabel("Last Page")
                .setStyle(ButtonStyle.Primary)
                .setDisabled(currentPage == maxPage) // Disable if user on last page
        );
    
        return components;
    
    }