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.
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.
https://www.youtube.com/watch?v=aWtvcNmrX80
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.
interaction.id
.currentPage
to track its changes.currentPage
.Full code of the command: https://pastebin.com/Ttv6YaY0
Part of the code used to handle button interactions and to update page indexes:
// 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!
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;
}