Home > Back-end >  TypeError [InvalidType]: Supplied roles is not a Role, Snowflake or Array or Collection of Roles or
TypeError [InvalidType]: Supplied roles is not a Role, Snowflake or Array or Collection of Roles or

Time:08-13

I'm having an issue creating my multiple select menu options on Discord.js v14.

I'm wanting to add more than one role to the member when more than one option is selected in the drop-down menu. However, I'm getting the following error:

TypeError [InvalidType]: Supplied roles is not a Role, Snowflake or Array or Collection of Roles or Snowflakes issue

If one role is selected it works fine and adds the role but when two roles are selected this error crops up.

This is the code I'm working with:

if (interaction.isSelectMenu()){
    if (interaction.customId == "optin_menu"){

        let optional_roles = "";
        await interaction.values.forEach(async value => {
            optional_roles  = `${value}` //the value being the name of the role
        
            const roles = interaction.guild.roles.cache.find(r => r.name === `${optional_roles}`); // Find the role with by name 
            const member = interaction.guild.members.cache.get(interaction.user.id); // The member we add the role too.

            member.roles.add(roles)


        });

        interaction.reply({content: `${optional_roles} added`, ephemeral: true}) 

Help appreciated.

CodePudding user response:

So there are several problems with your code.

The first thing is you try to use the operation_roles variable to store a list of role names (I think). But you also use it to find the role (r => r.name === optional_roles). If the select menu values are "Role 1", "Role 2" and "Role 3", on the first iteration operation_roles is "Role 1", on the second one it will be "Role 1 Role 2" and on the third one it becomes "Role 1 Role 2 Role 3". You won't be able to find roles with those names.

You will want to find the role by value, like this:

// Find the role with by name 
const role = interaction.guild.roles.cache.find(
  r => r.name === value
); 

Another error is that you don't handle cases when roles is not a Role, so member.roles.add will throw a TypeError; "Supplied roles is not a Role, Snowflake or Array or Collection of Roles or Snowflakes issue".

if (!role || !member) {
  // do something if either member or role not available
  return null;
}
member.roles.add(role);

Next, await won't do anything in front of that forEach.

Another error is that you try to find the member who selected the items in the menu (interaction.user.id). You'll want to get the member whose roles you want to update.

I'd suggest you use the role ID instead of the name as the value of the select menu. Not sure what your current code looks like, but I'd use something like this:

const row = new ActionRowBuilder()
  .addComponents(
    new SelectMenuBuilder()
      .setCustomId('optin_menu')
      .setPlaceholder('Select role(s)')
      .setMinValues(1)
      .setMaxValues(25)
      .addOptions(
        interaction.guild.roles.cache.first(25).map((role) => ({
          label: role.name,
          description: role.name,
          // use role.id instead
          value: role.id,
        })),
      ),
);

When you receive the interaction with the customId optin_menu you can wait for all the roles to be added first. You can create an array of promises and use Promise.all() to resolve these. You could return the role names from these promises and later just send them in your interaction.reply():

if (interaction.customId === 'optin_menu') {
  const promises = interaction.values.map(async (value) => {
    // Find the role by its ID instead
    const role = interaction.guild.roles.cache.get(value);
    const member = interaction.guild.members.cache.get('memberID');

    if (!role || !member)
      return null;

    // wait for the role to be added
    await member.roles.add(role);

    // return the role name
    return role.name;
  });

  // wait for the promises to resolve
  const rolesAdded = await Promise.all(promises);

  // send a message and join the roles by a comma
  interaction.reply({
    content: `The following roles are added: ${rolesAdded
      .filter(Boolean)
      .join(', ')}`,
    ephemeral: true,
  });
}

Now, you'll need to find out how to pass the member and use it inside if (interaction.customId === 'optin_menu'). One quick hack would be to add the member ID to your select menu's value. In this example, I just create a string with the role ID and the member ID separated by a -. This way you could later split it and use both IDs.

If you have a slash command like this:

new SlashCommandBuilder()
  .setName('addrole')
  .setDescription('Add roles to a member')
  .addUserOption((option) =>
    option
      .setName('target')
      .setDescription('Select a user')
      .setRequired(),
  )

Something like this should work:

client.on('interactionCreate', async (interaction) => {
  if (interaction.commandName === 'addrole') {
    // as we added .setName('target') above we use .getMember('target')
    const member = interaction.options.getMember('target');
    const row = new ActionRowBuilder().addComponents(
      new SelectMenuBuilder()
        .setCustomId('optin_menu')
        .setPlaceholder('Select role(s)')
        .setMinValues(1)
        .setMaxValues(25)
        .addOptions(
          interaction.guild.roles.cache.first(25).map((role) => ({
            label: role.name,
            description: role.name,
            value: `${role.id}-${member.id}`,
          })),
        ),
    );

   interaction.reply({
      content: `Please, select at least one role to add it to ${member}!`,
      components: [row],
    });
  }

  if (interaction.customId === 'optin_menu') {
    const promises = interaction.values.map(async (value) => {
      const [roleID, memberID] = value.split('-');
      const role = interaction.guild.roles.cache.get(roleID);
      const member = interaction.guild.members.cache.get(memberID);

      if (!role || !member) return null;

      await member.roles.add(role);

      return role.name;
    });

    let rolesAdded = await Promise.all(promises);

    interaction.reply({
      content: `The following roles are added: ${rolesAdded
        .filter(Boolean)
        .join(', ')}`,
      ephemeral: true,
    });
  }
});

enter image description here

CodePudding user response:

if (!interaction.isSelectMenu()) {
    return
}


if (customId === 'optin_menu') {
    
    const component = interaction.component
    const removed = component.options.filter((option) => {
        return !values.includes(option.value)
    })
    
    for (const id of removed) {
        const role = interaction.guild.roles.cache.find(r => r.name === `${id.value}`);
        member.roles.remove(role)
    }


    for (const id of values) {
        const role2 = interaction.guild.roles.cache.find(r => r.name === `${id}`);
        member.roles.add(role2)
    }

    interaction.reply({
        content: 'Roles updated!',
        ephemeral: true
    })
};
  • Related