# Select menus

Answer a select menu pick, from a string menu you write the options for to the menus Discord resolves for you. Covers routing the pick, the resolving kinds, and one handler serving several menus.

A row holds five buttons. Once a choice needs more than five, or someone should pick several things at once, a select menu fits better. It's a dropdown of up to 25 options.

One kind carries options you write yourself. For the rest, Discord resolves the picks into users, roles, channels, or a mix of users and roles, and sends the objects behind those ids. You skip asking for an id as text and fetching it yourself. Every kind gets its own handler base, and each one routes the way a button does.

```ts title="src/components/TopicPicker.ts"
import { StringSelectMenuBuilder } from '@discordjs/builders';
import { CustomId, RowComponent } from '@seedcord/gateway';

export const TopicsId = new CustomId('topics').snowflake('userId');

export class TopicPicker extends RowComponent<'menu_string'> {
    constructor(userId: string) {
        super('menu_string');

        this.instance.addComponents(
            new StringSelectMenuBuilder()
                .setCustomId(TopicsId.encode({ userId }))
                .setPlaceholder('Pick your topics')
                .setMinValues(1)
                .setMaxValues(3)
                .addOptions(
                    { label: 'Releases', value: 'releases' },
                    { label: 'Outages', value: 'outages' },
                    { label: 'Events', value: 'events' }
                )
        );
    }
}
```

`setMinValues(1)` and `setMaxValues(3)` above let someone pick one topic, two, or all three. Both default to 1, and Discord caps each at 25. A select menu takes a whole row. Discord rejects a message that puts a button or a second menu beside it.

## Routing the pick

The menu above carries options you wrote, so it routes through [`@StringMenuRoute`](https://docs.seedcord.org/packages/core/latest/string-menu-route) to a [`StringMenuHandler`](https://docs.seedcord.org/packages/gateway/latest/string-menu-handler). Pass the same [`CustomId`](https://docs.seedcord.org/packages/custom-id/latest/custom-id) definitions to the decorator and the generic. Passing different ones is a compile error.

```ts title="src/handlers/Topics.ts"
import {
    CustomId,
    StringMenuHandler,
    StringMenuRoute
} from '@seedcord/gateway';

// in your project this is defined in the component file that mints it
const TopicsId = new CustomId('topics').snowflake('userId');

@StringMenuRoute(TopicsId)
export class Topics extends StringMenuHandler<[typeof TopicsId]> {
    public async execute(): Promise<void> {
        const { userId } = this.params;
        const picked = this.values;

        await this.update(
            `<@${userId}> now follows ${picked.join(', ')}.`
        );
    }
}
```

`this.values` holds what someone picked. On a string menu those are the `value` strings you set on the options, like `'releases'`. `this.params`, `this.match`, `this.update`, and `this.deferUpdate` all work as they do on [Buttons](/components/buttons).

> **Gateway only**
>
> Every gateway menu base takes discord.js's `CacheType` as a second type argument, defaulting to `'cached'`. [Buttons](/components/buttons) explains what that default claims about a DM. An http menu base takes only the route tuple.

## The resolving kinds

The resolving kinds fill `this.values` with ids and carry the objects those ids point at. A user menu routes through [`@UserMenuRoute`](https://docs.seedcord.org/packages/core/latest/user-menu-route) to a [`UserMenuHandler`](https://docs.seedcord.org/packages/gateway/latest/user-menu-handler), which declares `this.users` and `this.members` beside the ids.

```ts title="src/handlers/Assign.ts"
@UserMenuRoute(AssignId)
export class Assign extends UserMenuHandler<[typeof AssignId]> {
    public async execute(): Promise<void> {
        const { roleId } = this.params;
        const chosen = this.users;

        await this.update(
            `Assigning <@&${roleId}> to ${chosen.size} member(s).`
        );
    }
}
```

Each base declares *only* the members its menu resolves.

{/* prettier-ignore-start */}

| base                                                                                                   | decorator                                                                                        | what the handler carries                                  |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------- |
| [`StringMenuHandler`](https://docs.seedcord.org/packages/gateway/latest/string-menu-handler)           | [`@StringMenuRoute`](https://docs.seedcord.org/packages/core/latest/string-menu-route)           | `this.values`                                             |
| [`UserMenuHandler`](https://docs.seedcord.org/packages/gateway/latest/user-menu-handler)               | [`@UserMenuRoute`](https://docs.seedcord.org/packages/core/latest/user-menu-route)               | `this.values`, `this.users`, `this.members`               |
| [`RoleMenuHandler`](https://docs.seedcord.org/packages/gateway/latest/role-menu-handler)               | [`@RoleMenuRoute`](https://docs.seedcord.org/packages/core/latest/role-menu-route)               | `this.values`, `this.roles`                               |
| [`ChannelMenuHandler`](https://docs.seedcord.org/packages/gateway/latest/channel-menu-handler)         | [`@ChannelMenuRoute`](https://docs.seedcord.org/packages/core/latest/channel-menu-route)         | `this.values`, `this.channels`                            |
| [`MentionableMenuHandler`](https://docs.seedcord.org/packages/gateway/latest/mentionable-menu-handler) | [`@MentionableMenuRoute`](https://docs.seedcord.org/packages/core/latest/mentionable-menu-route) | `this.values`, `this.users`, `this.members`, `this.roles` |

{/* prettier-ignore-end */}

If you read a member the base leaves out, like `this.channels` on a `UserMenuHandler`, TypeScript reports an error on that property.

Discord resolves `this.members` only inside a guild, so it comes back empty in a DM. A mentionable menu accepts users and roles together. `this.users` and `this.roles` each hold whatever matched, so either one can be empty.

> **Gateway and http differ**
>
> Both transports return a `Collection` keyed by id. The values differ. Gateway returns live discord.js instances, and http returns the payloads Discord sent.
>
> {/* prettier-ignore-start */}
>
> | getter          | gateway, `'cached'`  | http                                    |
> | --------------- | -------------------- | --------------------------------------- |
> | `this.users`    | `User`               | `APIUser`                               |
> | `this.members`  | `GuildMember`        | `APIInteractionDataResolvedGuildMember` |
> | `this.roles`    | `Role`               | `APIRole`                               |
> | `this.channels` | a discord.js channel | `APIInteractionDataResolvedChannel`     |
>
> {/* prettier-ignore-end */}

## One handler, several menus

Two menus of the same kind can share one class. Name every id on the decorator, then give `this.match` an arm per prefix.

```ts title="src/handlers/Filters.ts"
@StringMenuRoute(RegionId, LanguageId)
export class Filters extends StringMenuHandler<
    [typeof RegionId, typeof LanguageId]
> {
    public async execute(): Promise<void> {
        const picked = this.values.join(', ');

        await this.match({
            region: ({ userId }) =>
                this.update(`<@${userId}> is in ${picked}.`),
            language: ({ userId }) =>
                this.update(`<@${userId}> reads ${picked}.`)
        });
    }
}
```

`picked` is read once, before `match`, and both arms use it. Share a class like this when both menus run the same code first. Two menus that share nothing read better as two handlers, one file each.

`this.match` takes one arm for each id in the generic, keyed by that id's prefix, so your editor offers `region` and `language`.

```ts
await this.match({ '
```

> **Warning**
>
> A string menu and a user menu need a handler each. If you point one kind's decorator at another kind's base, TypeScript reports "the decorator does not match the handler kind" on the decorator line.

Putting several menus on one message takes a row each. Give each menu its own id. Route them to the same handler when they share a kind.
