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!`
})
}