Disable a button using its custom_id
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:
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.040 Replies
- What's your exact discord.js
npm list discord.js
and node node -v
version?
- Not a discord.js issue? Check out #other-js-ts.
- 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 OPWhen 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 dataHi, 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)
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 ActionRowNo this is not the case, you'll receive all the action rows from the bot' msg
Weird, lemme test it again. I only got 1 ActionRow back even though I sent the embed with 4 ActionRows
Alright
If it still persists, show me your code
Sure, thanks for the help so far - it is very much appreciated 👍 Getting these buttons to work has eaten up the better part of two days now
Haha no worries, happens to everyone once a while
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?
Button Builder would be the class from djs
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 collectorYes exactly
Will give it a shot, thanks once again for your help 👍
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.)
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/rewriteCan't you just loop through message.components and then loop through each of them to change the disabled to true instead of doing all this hassle?
Documentation suggestion for @Toldi: Message#components
An array of action rows in the message. (more...)
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.That's more of a bad habit I picked up after running into issues with const - will replace the var's with
let
's.
Yeah, I realized I am not grabbing onto it correctly, trying to refactor my code nowGood 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
Minor success in finally being able to disable all buttons in one go after a single button is clicked:
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:
Now I just need to figure out how to run the last for loop once the 'end' collector emits.
I don't get what you mean but I assume you don't need help now am I right?
Might need help with the 'end' collector. When you use
createMessageComponentCollector()
, you can set a time (for when the the bot should stop collecting). But the message that is emitted when that timer runs out is user original message and not the message that the bot sentYou send the bot message in the same code right? so just use it
show code
This is what I have in the collector on 'end':
(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
So what's left is to edit the message no? ButtonInteraction#message#edit
The edit is in there (code got truncated when I copy pasted), I am just not grabbing onto the components inside the collection map correctly - messing around with that now. The
Collection(1) [Map] {...}
is throwing me off from the messageWhen 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 thencollection | Collection
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.
Btw, thanks for all your help so far. I really appreciate it 👍
Show code
Just running this (while I make sense of map and collectors):
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)why do you always define the button interaction as message, that will just confuse you even more
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.Ah, that's because I use both slash commands (where you would get an interaction) and message listeners (so if a user started a message with a certain prefix then the bot listens to that and runs
will try that, thanks 👍
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:
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:
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
No need to store any data, just filter out the disabled buttons current.data.disabled iirc
Stepped away from the laptop for 2 hours and thought of the same thing, thanks for all the help 👍 looks like it is working as it should. Adding the 'OR' to check if it was disabled, and if it was, then keep it disabled as I loop over it.