# Buttons

Answer a button click with ButtonHandler, reading the values its custom id carries. Covers rewriting the message the click came from, and one handler serving several buttons.

A click arrives as its own interaction carrying the custom id your component minted. [`ButtonHandler`](https://docs.seedcord.org/packages/gateway/latest/button-handler) decodes that id and puts the values on [`this.params`](https://docs.seedcord.org/packages/gateway/latest/component-handler#params).

```ts title="src/handlers/Appeal.ts"
import {
    ButtonHandler,
    ButtonRoute,
    CustomId
} from '@seedcord/gateway';

// in your project, write this in the component file that builds the button
const AppealId = new CustomId('appeal').snowflake('userId');

@ButtonRoute(AppealId)
export class Appeal extends ButtonHandler<[typeof AppealId]> {
    public async execute(): Promise<void> {
        const { userId } = this.params;

        await this.reply(`Appeal filed for <@${userId}>.`);
    }
}
```

[`@ButtonRoute`](https://docs.seedcord.org/packages/core/latest/button-route) takes the [`CustomId`](https://docs.seedcord.org/packages/custom-id/latest/custom-id) your component used, and the generic lists the same one as a tuple. Passing the decorator one definition and the generic another is a compile error.

A click is an interaction like any other, so every [reply method](/replying) works here.

> **Gateway only**
>
> A gateway `ButtonHandler` takes a second type argument, discord.js's `CacheType`, which defaults to `'cached'`. That default types `this.event.member` as a `GuildMember`, and seedcord doesn't check it at runtime. A button in a DM has no member, so pass `'raw'` or `CacheType` for a handler that can get one. An http `ButtonHandler` takes only the route tuple.

## Rewriting the source message

A `reply` to a click posts a new message under the old one. The old buttons stay there, so someone can click them again.

[`this.update()`](https://docs.seedcord.org/packages/gateway/latest/component-handler#update) rewrites the message the button came from. It takes the same response shape every reply method takes, so a new `components` list replaces the old buttons.

```ts
const { userId } = this.params;

await this.update({
    components: [
        new ReviewCard(userId, 'under review').component
    ]
});
```

Discord shows "This interaction failed" on a click that nobody answers within three seconds. For a click that changes nothing on the message, [`this.deferUpdate()`](https://docs.seedcord.org/packages/gateway/latest/component-handler#defer-update) answers it and leaves the message unchanged.

> **Gateway and http differ**
>
> `update()` resolves to a discord.js `Message` on gateway and an
> `APIMessage` on http.

## One handler, several buttons

Approve and reject sit next to each other on the same message, and one handler can serve both. Pass every id to the decorator, list them in the generic, then branch with [`this.match`](https://docs.seedcord.org/packages/gateway/latest/component-handler#match).

```ts title="src/handlers/Review.ts"
@ButtonRoute(ApproveId, RejectId)
export class Review extends ButtonHandler<
    [typeof ApproveId, typeof RejectId]
> {
    public async execute(): Promise<void> {
        await this.match({
            approve: ({ userId }) =>
                this.update(`Approved <@${userId}>.`),
            reject: ({ userId, reason }) =>
                this.update(`Rejected <@${userId}>. ${reason}`)
        });
    }
}
```

You key each arm by its route's prefix, and each one receives that route's own values. `reject` reads a `reason` that `approve` never declared. Both arms pass `update` a plain string, which seedcord wraps in one text component. Leave an arm out and the build stops, since the compiler requires one per registered prefix.

Keep two buttons in one handler when both arms need the same code first, like a check that the clicker is staff. Two buttons that share nothing belong in two handlers, where each file holds only the code its own button runs.

> **Warning**
>
> `this.params` is `never` on a handler registered for several routes, since the decoded shape depends on which button someone clicked. `this.match` is the only read there.
>
> Decoding runs once, before any arm, so a wire from an [older shape](/components/stale) throws before your code executes.
