#Disable a button using its custom_id

57 messages ยท Page 1 of 1 (latest)

weak hollow
#

Can I disable a button (that has not been assigned to a variable) by using it's custom_id? I am creating buttons dynamically with a for loop with the following code:

if(similarMatchesList){
        for (let a = 0; a < similarMatchesList.length; a++){
            const current = similarMatchesList[a];
            buttons.push(
                new ButtonBuilder()         
                     .setCustomId(`${buttonPrefix}${current.id}`) 
                     .setLabel(`${current.name} [${current.id}]`)
                     .setStyle(ButtonStyle.Secondary)
            );
        }
    }

I am able to pass the buttons into an ActionRowBuilder. Then send the ActionRowBuilder with the message correctly, and I am able to "collect" when the button is interacted with.

But I am struggling with disabling the button (adding setDisabled(true) to it) that was just clicked since I don't have a variable associated with the button. I was hoping to disable the button using it's custom_id, but haven't been able to find a way to do that.

I thought about assigning each button to a new variable as I go through the for loop, but haven't been able to think of a way of doing that.

Thanks for the help in advance!

Discord.js version: 14.14.1
Node version: 20.8.0

plucky waveBOT
#
  • What's your exact discord.js npm list discord.js and node node -v version?
  • Not a discord.js issue? Check out #1081585952654360687.
  • Consider reading #how-to-get-help to improve your question!
  • Explain what exactly your issue is.
  • Post the full error stack trace, not just the top part!
  • Show your code!
  • Issue solved? Press the button!
  • โœ… Marked as resolved by OP
river terrace
#

When do you disable the button? After you click it or before?

#

Oh nevermind

#

Anyway, you get a ButtonInteraction when a button is clicked.

  • You can use ButtonInteraction.message to get the message that was associated to the button that was clicked.
  • Now, you can all your action rows by using message.components which returns an array of action rows.
  • Loop over each action row and access the component property from ActionRow.components
  • Use the ButtonBuilder.from() method to convert each button into a builder
  • Find the button you want to disable by comparing the customId
  • Update the interaction with the new data
weak hollow
# river terrace Anyway, you get a `ButtonInteraction` when a button is clicked. - You can use ...

Hi, thanks for your detailed reply! Stil a bit lost because when I grab onto the message.message.components of the ButtonInteraction I am only getting a single ActionRow, even though I initially sent more (I assume those get returned if I click on another button that is contained within another ActionRow?). But then if a button isn't clicked in those ActionRows, how would I go about disabling the remaining buttons? I might be going about this backwards...

What I am trying to do is when people search for a unit from a game, there might be similar named matches that I want to return (as separate buttons, so the user can click on the button and get a new embed with new unit info).

I need to be able to both disable the button that was clicked (sometimes multiple buttons) and then disable all the remaining buttons at some point so that people can't click it once the time (the one in ms) that I set runs out.

#

A demo of how it looks like:

#

And then clicking one of the buttons should just return an embed with no further buttons (so don't need to send buttons again for that as well)

river terrace
#

If it's always a single action row, you can just access the first action row.... Otherwise use a loop

#

I assume those get returned if I click on another button that is contained within another ActionRow

No this is not the case, you'll receive all the action rows from the bot' msg

weak hollow
river terrace
#

Alright

#

If it still persists, show me your code

weak hollow
river terrace
#

Haha no worries, happens to everyone once a while

weak hollow
#

Ran it again for one of the commands that returns 25 buttons (in 5 Action Rows) and now I got 5 Action Rows returned with 5 buttons in each:

#

Thinking through your suggestion to use ButtonBuilder.from() - in this case from you mean the JS Array.from() method, right?

river terrace
#

Button Builder would be the class from djs

weak hollow
#

So the point of using the ButtonBuilder.from() on each of the buttons is to recreate the buttons, but this time have setDisabled(true) so that they get disabled. And this is what I would do to wrap up once I get the 'end' message for the collector

river terrace
#

Yes exactly

weak hollow
river terrace
#

Sure no problem

weak hollow
# river terrace Sure no problem

Been trying to get this to work for the last three days, but haven't been able to quite figure it out. Currently stuck on this:

(This is inside my collector that triggers on 'collect' - it will be moved to the collector that triggers on 'end', but since that emits/returns my original message and not the bot message with the embed and buttons, I will need to refactor it.)

var buttonArray = Array.from(message.message.components.ActionRow.components);
var arrayLength = buttonArray.length;
console.log('Array length: ' + arrayLength);
const buttons = [];
for (var i = 0; i < arrayLength; i++){
  const current = buttonArray[i]
    buttons.push(
      new ButtonBuilder()
        .setCustomId(`${i}`)
        .setLabel(`${i}`)
            .setStyle(ButtonStyle.Secondary),
            .setDisabled(true)
      );
  }
// The buttonWrapper function takes an array of buttons and turns them into rows with the help of ActionRowBuilder
const buttonsArray = buttonWrapper(buttons);

response.edit({
  components: buttonsArray
})

I am stuck on correctly using the Array.from() and properly selecting the buttons from inside the ActionRow. Based on how nested the buttons are, I thought I was grabbing onto them with message.message.components.ActionRow.components, but I think I am wrong. Console.log-ing that usually gives undefined. Probably I am being blind to the obvious solution/rewrite

urban ridge
sick vigilBOT
urban ridge
#

Your first line seems to be invalid as components is an array of action rows

#

and I don't really know why you still use var? but that's not the topic here.

weak hollow
weak hollow
urban ridge
#

Good luck and let us know if you still face any issues

weak hollow
# urban ridge Good luck and let us know if you still face any issues

Thanks, will do. It's just a bit too much with the array inside the array inside the array - russian doll style ๐Ÿคฃ even when I go to message.message.components I still have to go inside an array that contains arrays of ActionRows, with each ActionRow having an array of data and an array of components inside it

weak hollow
#

Minor success in finally being able to disable all buttons in one go after a single button is clicked:

let arrayOfActionRows = message.message.components;
const buttons = [];
for (const actionRow of arrayOfActionRows){
  const componentsRow = actionRow.components;
  console.log(componentsRow[0].data);
  for (let a = 0; a < componentsRow.length; a++){
    const current = componentsRow[a];
    buttons.push(
      new ButtonBuilder()
            .setCustomId(`${current.data.custom_id}`)
            .setLabel(`${current.data.label}`)
            .setStyle(ButtonStyle.Secondary)
            .setDisabled(true)
    );
}

Now I'll try to add the setDisabled(true) to only the button that was actually clicked (probably through an if statement? not sure on that yet) and then try to run the last for loop once the 'end' is emitted (although that doesn't contain the bot button information, so will need to get creative for that)

#

I am disabling only the clicked button with this:

  for (const actionRow of arrayOfActionRows){
    const ComponentsRow = actionRow.components;
    for (let a = 0; a < ComponentsRow.length; a++){
      const current = ComponentsRow[a];
        if(messageCustomID === current.data.custom_id){
          const buttonBuilder = new ButtonBuilder()
            .setCustomId(`${current.data.custom_id}`)
            .setLabel(`${current.data.label}`)
            .setStyle(ButtonStyle.Secondary)
            .setDisabled(true);
          buttons.push(buttonBuilder);
        } else {
          const buttonBuilder = new ButtonBuilder()
            .setCustomId(`${current.data.custom_id}`)
            .setLabel(`${current.data.label}`)
            .setStyle(ButtonStyle.Secondary)
            .setDisabled(false);
         buttons.push(buttonBuilder)
    };
}

#

Now I just need to figure out how to run the last for loop once the 'end' collector emits.

urban ridge
weak hollow
urban ridge
#

You send the bot message in the same code right? so just use it

#

show code

sick vigilBOT
weak hollow
# urban ridge show code

This is what I have in the collector on 'end':

collector.on('end', (message) => {
  let arrayOfActionRows = message.message.components;
  const buttons = [];
  for (const actionRow of arrayOfActionRows){
    const componentsRow = actionRow.components;
    console.log(componentsRow[0].data);
    for (let a = 0; a < componentsRow.length; a++){
      const current = componentsRow[a];
      buttons.push(
        new ButtonBuilder()
            .setCustomId(`${current.data.custom_id}`)
            .setLabel(`${current.data.label}`)
            .setStyle(ButtonStyle.Secondary)
            .setDisabled(true)
    );
  }
  const buttonsArray = buttonWrapper(buttons);

  response.edit({
    components: buttonsArray
  })
})
#

(the same code that worked when I was testing the on('collect'))

#

Ah... the message that is returned on 'end' is a collection map with slightly different structure then the button interaction message

urban ridge
# weak hollow

So what's left is to edit the message no? ButtonInteraction#message#edit

weak hollow
#

When I run console.log(message.map) I get back [Function: map], which led me here: https://discord.js.org/docs/packages/collection/1.5.1/Collection:Class But I am not sure it is relevant or how to make sense of it (been staring at this for too long in one sitting). Might leave it for the morning and tackle it again then

A Map with additional utility methods. This is used throughout discord.js rather than Arrays for anything that has an ID, for significantly improved performance and ease-of-use.

weak hollow
weak hollow
# urban ridge Show code

Just running this (while I make sense of map and collectors):

collector.on('end', (message) => {
  console.log(message);
})

And the message is what returns the Collection(1) [Map] { 'messageId' => ButtonInteraction { ... }. I saw in the documentation that I am supposed to map over it, but it is just not clicking to me what I should be mapping over to get to the components (with the ActionRows with the buttons)

urban ridge
#

The map is what returned from the collector as a result to your filter applied to it. so if you do console.log(message.first()) you should get the first button clicked.

weak hollow
weak hollow
weak hollow
# urban ridge The map is what returned from the collector as a result to your filter applied t...

This really helped, thanks once again!

When I was reading the documentation I guess the words just weren't making sense to me and I kept looking it over without getting anywhere. I grabbed onto the interaction with:

 const arrayOfActionRows = message.first().message.components;

and then I was able to run my previous code that went through the buttons and disabled them one by one once the timer ran out.

#

Now I am just refactoring this:

let arrayOfActionRows = message.message.components;
const buttons = [];
for (const actionRow of arrayOfActionRows){
  const ComponentsRow = actionRow.components;
  for (let a = 0; a < ComponentsRow.length; a++){
    const current = ComponentsRow[a];
      if(messageCustomID === current.data.custom_id){
        const buttonBuilder = new ButtonBuilder()
          .setCustomId(`${current.data.custom_id}`)
          .setLabel(`${current.data.label}`)
          .setStyle(ButtonStyle.Secondary)
          .setDisabled(true);
        buttons.push(buttonBuilder);
      } else {
        const buttonBuilder = new ButtonBuilder()
          .setCustomId(`${current.data.custom_id}`)
          .setLabel(`${current.data.label}`)
          .setStyle(ButtonStyle.Secondary)
          .setDisabled(false);
       buttons.push(buttonBuilder)
  };
  }
}
const buttonsArray = buttonWrapper(buttons);

response.edit({
  components: buttonsArray
})

Since the way I have it now, when I click the first button, it gets disabled correctly, but if I click a second button, I inadvertently reenable the first clicked button (and disable the second button that was clicked)

#

Thinking about storing the custom_id of the clicked buttons in an array and that matches, then disabling those buttons as well

urban ridge