3

This is how I create and send the button:

client.on('messageCreate', (message) => {
    /* ... Checking Command ... */

    const actionRow = new MessageActionRow().addComponents(
        new MessageButton()
        .setStyle("PRIMARY")
        .setLabel("X")
        .setCustomId("test"));


    message.channel.send({ content: "Test", components: [actionRow] });
}

A blue Button appears in the chat, as expected.

This is my Button-Listener:

client.on("interactionCreate", (interaction) => {
    if (interaction.isButton()) {
        if (interaction.customId === "test") {
            //Before: console.log(interaction.component);

            interaction.component.setStyle("DANGER");

            //After: console.log(interaction.component);
        }
    }
});

Logging the component-object before and after .setStyle("DANGER") also reveals, that the style got changed from Primary to Danger successfully.

But in my Discord Client, the Style/Color didn't change, and ontop of that I am getting an error, saying that the interaction failed.

The style-property doesn't seem to be read-only: https://discord.js.org/#/docs/main/stable/class/MessageButton?scrollTo=style

So what am I doing wrong?

FireFuro99
  • 357
  • 1
  • 5
  • 18

3 Answers3

3

You updated the style only locally, you didn't send the changed component back to the Discord API.

To get rid of the error "This interaction failed", you need to respond to the interaction. One way to respond is to use MessageComponentInteraction.update(), which updates the original message.

client.on("interactionCreate", (interaction) => {
    if (interaction.isButton()) {
        if (interaction.customId === "test") {

            // Change the style of received button component
            interaction.component.setStyle("DANGER");

            // Respond to the interaction,
            // and send updated component to the Discord API
            interaction.update({
                components: [
                    new MessageActionRow().addComponents(interaction.component)
                ]
            });

        }
    }
});

enter image description here

To make this work with multiple buttons, use the example below.

client.on("interactionCreate", (interaction) => {
    if (interaction.isButton()) {
        // Make this work only for certain buttons,
        // with IDs like switch_0, switch_1, etc.
        if (interaction.customId.startsWith("switch_")) {

            // Change the style of the button component,
            // that triggered this interaction
            interaction.component.setStyle("DANGER");

            // Respond to the interaction,
            // and send updated components to the Discord API
            interaction.update({
                components: interaction.message.components
            });

        }
    }
});

enter image description here

Skulaurun Mrusal
  • 2,792
  • 1
  • 15
  • 29
  • Will this work with multiple buttons? – K.K Designs May 17 '22 at 02:04
  • @K.KDesgins I have updated the answer accordingly, if you want to update all the buttons at once, just iterate `interaction.message.components` and change the style of every corresponding "switch" button to `"DANGER"`. – Skulaurun Mrusal May 18 '22 at 14:45
2

For any future viewers who might be using Discordjs V14+ you can't edit the components directly anymore, so you need to recreate them in order to edit them. This is a solution I came up with that flips the color when clicked!

const collector = interaction.channel.createMessageComponentCollector({ time: 15000 });
collector.on('collect', async i => {
  //loop through each action row on the embed and update it accordingly
  let newActionRowEmbeds = i.message.components.map(oldActionRow => {

    //create a new action row to add the new data
    updatedActionRow = new ActionRowBuilder();
    
    // Loop through old action row components (which are buttons in this case)
    updatedActionRow.addComponents(oldActionRow.components.map(buttonComponent => {

      //create a new button from the old button, to change it if necessary
      newButton = ButtonBuilder.from(buttonComponent)
      
      //if this was the button that was clicked, this is the one to change!
      if(i.component.customId == buttonComponent.customId){

        //If the button was a primary button then change to secondary, or vise versa
        if(buttonComponent.style == ButtonStyle.Primary){
          newButton.setStyle(ButtonStyle.Secondary)
        }
        else if (buttonComponent.style == ButtonStyle.Secondary){
          newButton.setStyle(ButtonStyle.Primary)
        }
      }
      return newButton
    }));
    return updatedActionRow
  });
  
  // and then finally update the message
  await i.update({components: newActionRowEmbeds})
});
0

Just some minor improvements to the solution of @LachyLegend for easier use.

TypeScript:

function updateComponent<T extends MessageActionRowComponentBuilder>(interaction: MessageComponentInteraction, newButtonFunc: (component: T) => T, customId = interaction.customId): ActionRowBuilder<MessageActionRowComponentBuilder>[] {
    const indices = findComponent(interaction, customId);
    if (!indices) {
        return [];
    }

    const actionRows = interaction.message.components.map<ActionRowBuilder<MessageActionRowComponentBuilder>>((row) => ActionRowBuilder.from(row));
    newButtonFunc(actionRows[indices.actionRowIndex].components[indices.componentIndex] as T);

    return actionRows;
}

function findComponent(interaction: MessageComponentInteraction, customId: string): {actionRowIndex: number, componentIndex: number} | undefined {
    const actionRows = interaction.message.components;
    for (let actionRowIndex = 0; actionRowIndex < actionRows.length; ++actionRowIndex) {
        const actionRow = actionRows[actionRowIndex];

        for (let componentIndex = 0; componentIndex < actionRow.components.length; ++componentIndex) {
            if (actionRow.components[componentIndex].customId === customId) {
                return {
                    actionRowIndex,
                    componentIndex,
                };
            }
        }
    }
}

JavaScript

function updateComponent(interaction, newButtonFunc, customId = interaction.customId) {
    const indices = findComponent(interaction, customId);
    if (!indices) {
        return [];
    }

    const actionRows = interaction.message.components.map((row) => ActionRowBuilder.from(row));
    newButtonFunc(actionRows[indices.actionRowIndex].components[indices.componentIndex]);

    return actionRows;
}

function findComponent(interaction, customId) {
    const actionRows = interaction.message.components;
    for (let actionRowIndex = 0; actionRowIndex < actionRows.length; ++actionRowIndex) {
        const actionRow = actionRows[actionRowIndex];

        for (let componentIndex = 0; componentIndex < actionRow.components.length; ++componentIndex) {
            if (actionRow.components[componentIndex].customId === customId) {
                return {
                    actionRowIndex,
                    componentIndex,
                };
            }
        }
    }
}

usage:

const reply = await interaction.reply(messageContent);
const collector = reply.createMessageComponentCollector({ componentType: ComponentType.Button, time: 3_600_000 });

collector.on('collect', async (buttonInteraction) => {
    const newActionRows = updateComponent(buttonInteraction, (button) => button.setStyle(button.data.style === ButtonStyle.Success ? ButtonStyle.Danger : ButtonStyle.Success));
    // updateButton<ButtonBuilder>(...) or updateButton<TheBuilderYouOverride>(...) for TypeScript

    await reply.edit({
        content: buttonInteraction.message.content,
        components: newActionRows,
    });
    buttonInteraction.update({});
});
Kirdock
  • 11
  • 3