Skip to content
Advertisement

Modal Collector Discord.js

I was trying to create a collector for my modal using Discord.js. With the code below I don’t get an error, but the modal fails in Discord and the code in collector.on never runs. I have used a similar approach to be able to create a button collector by replaycing the compententType: with "BUTTON", which runs perfectly fine. I am not sure if there is a different way to listen for modals but I cant find much about it in the documentation.

//create modal and input field and display them
const testModal = new Modal()
      .setCustomId("test_modal")
      .setTitle("Test")
                    
const input = new Discord.MessageActionRow().addComponents(
      new Discord.TextInputComponent()
            .setCustomId("test_input")
            .setLabel("Test Input:")
            .setStyle("SHORT")
            .setPlaceholder("Input Something")
            .setRequired(true)
)
                      
testModal.addComponents(input)
await interaction.showModal(testModal)


//create collector 
const collector = await interaction.channel.createMessageComponentCollector({ componentType: 'TEXT_INPUT', time: 15000 })

//listen to collector 
await collector.on("collect", modal=>{
    if (modal.isModalSubmit() && modal.customId === 'test_modal') { //only left side
        console.log(modal)
        modal.reply("Modal collected")
    }
})

Thanks for your help in advance

Advertisement

Answer

Discord.JS implementation for Modals is a little bit tricky (or at least, unintuitive if you have experience with handling interactions for buttons, etc). To summarize, the Interaction you get when the Discord Client receives a Modal does not extend the same type of Interaction you get when you receive a MessageComponentInteraction (note the subtext: ‘extends Interaction implements InteractionResponses’) (e.g. a ButtonInteraction (note the subtext: ‘extends MessageComponentInteraction’), SelectMenuInteraction, etc). As such, the createMessageComponentCollector does not ever receive Interactions that hold a ModalSubmitInteraction (notice the subtext: ‘extends Interaction implements InteractionResponses’)— since a ModalSubmitInteraction is not a MessageComponentInteraction (the former examples ButtonInteraction and SelectMenuInteraction are).

The ModalSubmitInteraction is to a Modal Interaction what a MessageComponentInteraction is to a ButtonInteraction. Does that make sense? ModalSubmitInteraction and MessageComponentInteraction can be considered as ‘siblings’, while ButtonInteraction and/or SelectMenuInteraction, etc would be considered as ‘children of MessageComponentInteraction’. For further clarification I would take a look at Discord’s Documentation for Modals, as it provides some context for how you’d use them with Discord.JS.

There are likely other ways to handle the ‘collection’ of a Modal, but the way I would do it in the example you posted is through the awaitModalSubmit method on the MessageComponentInteraction class. For example:

const fields = {
  age: new TextInputComponent()
    .setCustomId(`age`)
    .setLabel(`What is your age?`)
    .setStyle(`SHORT`)
    .setRequired(true),
    .setPlaceholder(`90 years young`)
  name: new TextInputComponent()
    .setCustomId(`name`)
    .setLabel(`What is your name?`)
    .setStyle(`SHORT`)
    .setRequired(true)
    .setPlaceholder(`John Doe`)
}

const modal = new Modal()
  .setCustomId(`test_modal`)
  .setTitle(`test`)
  .setComponents(
    // Note that unlike how you might expect when sending a Message with Components,
    // MessageActionRows for Modals **can only accept TextInputComponents** (no Buttons or
    // SelectMenus or other Components), and each Action Row can have a maximum of just one
    // TextInputComponent. You can have a maximum of 5 Action Rows in a Modal, so you have
    // a maximum of 5 Text Inputs per Modal.
    new MessageActionRow().setComponents(fields.age),
    new MessageActionRow().setComponents(fields.name),
  )

// Show the Modal to the User in response to the Interaction
await interaction.showModal(modal)

// Get the Modal Submit Interaction that is emitted once the User submits the Modal
const submitted = await interaction.awaitModalSubmit({
  // Timeout after a minute of not receiving any valid Modals
  time: 60000,
  // Make sure we only accept Modals from the User who sent the original Interaction we're responding to
  filter: i => i.user.id === interaction.user.id,
}).catch(error => {
  // Catch any Errors that are thrown (e.g. if the awaitModalSubmit times out after 60000 ms)
  console.error(error)
  return null
})

// If we got our Modal, we can do whatever we want with it down here. Remember that the Modal
// can have multiple Action Rows, but each Action Row can have only one TextInputComponent. You
// can use the ModalSubmitInteraction.fields helper property to get the value of an input field
// from it's Custom ID. See https://discord.js.org/#/docs/discord.js/stable/class/ModalSubmitFieldsResolver for more info.
if (submitted) {
  const [ age, name ] = Object.keys(fields).map(key => submitted.fields.getTextInputValue(fields[key].customId))
  await submitted.reply({
    content: `Your age is ${age}, and your name is ${name}. Hi!`
  })
}
User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement