grammyjs / menu

Interactive menus for grammY.
https://grammy.dev/plugins/menu
MIT License
31 stars 11 forks source link

replace buttons instead append (duplicated buttons) #43

Closed JeffersonTeixeira closed 5 months ago

JeffersonTeixeira commented 1 year ago

When I have something like:

                 menu.dynamic((ctx, range) => {
                    test.map.forEach((t) => {
                      range 
                        .text(t.desc, (ctx) => ctx.reply(t.desc))
                        .row();
                    });
                  });

ctx.reply("Select...", { reply_markup: menu });

executed everytime by a command it is appending new button to the menu, so I have it duplicated how can I replace all buttons instead add? Or maybe remove all buttons before add new one

KnorpelSenf commented 1 year ago

This is caused by a misuse of the plugin.

You must build the menu once upfront. What you are doing is to add dynamic ranges every time your handler runs, which just makes the menu bigger and bigger.

You can fix it by moving the call to menu.dynamic outside of your middleware.

JeffersonTeixeira commented 1 year ago

I must render the menu according the parameter passed to command ex: /teste A /teste B /teste C

there is a lot of possible parameters, and buttons are different between themselves. I don't want to create a menu for each group of possibility. Is there any workaround to delete buttons? When the user choose I don't need the menu anymore

KnorpelSenf commented 1 year ago

buttons are different between themselves

This is what dynamic labels and dynamic ranges are for. They let you generate different buttons every time the menu is used.

When the user choose I don't need the menu anymore

This is an illusion. There will be a next user who wants to see the menu again, which means that you will have to recreate the menu at that point. However, menus cannot be created from handlers. You must create them upfront and install them. Then you can send them to as many users as you want.

KnorpelSenf commented 1 year ago

So in principle, your structure should look something like this:

const menu = new Menu<MyContext>("test map")
menu.dynamic((ctx, range) => {
  ctx.session.test.map.forEach((t) => {
    range 
      .text(t.desc, (ctx) => ctx.reply(t.desc))
      .row();
  });
});
bot.use(
  session({ initial: () => ({ test: { map: [] } }) }),
  menu,
)

bot.command("teste", async ctx => {
  ctx.session.test = test; // whatever test is for you right now
  await ctx.reply("there you go", { reply_markup: menu })
})

Note that using sessions is not required, you just somehow have to be able to access the same data in your dynamic range, irrespective of whether you send the menu from a command handler or whether the menus handles a callback query. In other words, you can avoid using sessions if you are able to install the session data on the context for callback queries and commands alike.

KnorpelSenf commented 5 months ago

This seems to be answered