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