JavaScript Discord Bot Multiple Reaction Handler Issue

I’m working on a JavaScript Discord bot and running into a problem with reaction handling. When I have multiple messages with reactions in the channel, the bot triggers the same reaction handler for all of them instead of just the specific message I want.

Basically I’m making a dice roller bot where users can click a reaction to reroll their dice. The bot correctly responds to reactions and removes the user’s reaction afterward, but if there are several messages with the same reaction emoji, it processes all of them at once.

Here’s my message handler:

bot.on('message', msg => {
  if(!msg.content.startsWith(prefix) || msg.author.bot) return;
  let commands = msg.content.substring(prefix.length).split(" ");
  
  switch (commands[0].toLowerCase()) {
    case "dice": case "d":
      if(!commands[1] || commands[1].indexOf("d") == -1) return msg.channel.send('Bad dice format');
      bot.commands.get('roller').ExecuteRoll(msg, commands, bot);
      break;
  }
});

And here’s the dice rolling module:

module.exports = {
  name: 'roller',
  description: 'Dice rolling system',
  async ExecuteRoll(msg, commands, botClient) {
    const targetChannel = 'YYYYYYYYYYYYYYYYYYY';
    const rollIcon = '🎲';
    
    if(!commands[2]) {
      var result = ProcessDice(commands[1]);
    } else if (commands[2] == "s" || commands[2] == "S") {
      var result = ProcessDice(commands[1], true);
    }
    
    let diceMessage = await msg.channel.send(result);
    diceMessage.react(rollIcon);
    
    botClient.on('messageReactionAdd', async (reactionEvent, userObj) => {
      if(reactionEvent.message.partial) await reactionEvent.message.fetch();
      if(reactionEvent.partial) await reactionEvent.fetch();
      if(userObj.bot) return;
      if(!reactionEvent.message.guild) return;
      
      if(reactionEvent.message.channel.id == targetChannel) {
        if(reactionEvent.emoji.name === rollIcon) {
          var freshRoll = ProcessDice(commands[1]);
          await msg.channel.send(freshRoll);
          await reactionEvent.users.remove(userObj);
        }
      }
    });
  }
}

How can I fix this so the bot only responds to reactions on the specific message and not every message that has the same reaction emoji?

You’re adding a new event listener every time someone rolls dice. These listeners pile up and check ALL reactions, not just your specific message. You need to check the message ID and clean up when you’re done.

Here’s a better way:

let diceMessage = await msg.channel.send(result);
diceMessage.react(rollIcon);

const reactionHandler = async (reactionEvent, userObj) => {
  if(reactionEvent.message.id !== diceMessage.id) return;
  if(userObj.bot) return;
  if(reactionEvent.emoji.name === rollIcon) {
    var freshRoll = ProcessDice(commands[1]);
    await msg.channel.send(freshRoll);
    await reactionEvent.users.remove(userObj);
  }
};

botClient.on('messageReactionAdd', reactionHandler);

// Remove listener after some time to prevent memory leaks
setTimeout(() => {
  botClient.removeListener('messageReactionAdd', reactionHandler);
}, 300000); // 5 minutes

The fix is reactionEvent.message.id !== diceMessage.id - this makes sure you only handle reactions on your dice message. Plus removing the listener prevents memory leaks from stacking handlers.

You’re stacking event listeners - that’s why it fires for everything. Move the messageReactionAdd listener outside ExecuteRoll and filter by message ID instead. if(reactionEvent.message.id !== diceMessage.id) return; works, but you’ll need to store the message IDs somewhere to track active dice rolls.

You’re creating a new messageReactionAdd listener inside ExecuteRoll every time someone runs the dice command. All these listeners stay active and respond to reactions server-wide.

I hit this same issue building a poll bot. Skip the dynamic listeners - use a message collector instead. It handles cleanup automatically:

let diceMessage = await msg.channel.send(result);
diceMessage.react(rollIcon);

const filter = (reaction, user) => {
  return reaction.emoji.name === rollIcon && !user.bot;
};

const collector = diceMessage.createReactionCollector({ filter, time: 300000 });

collector.on('collect', async (reaction, user) => {
  var freshRoll = ProcessDice(commands[1]);
  await msg.channel.send(freshRoll);
  await reaction.users.remove(user);
});

Collectors are perfect for this. They only listen to reactions on that specific message, clean up when they timeout, and won’t cause the memory leak you’re dealing with.