docs/05-message-components.md
# Message Components
Discord Message Components are interactable elements that appear in a message. Currently, there are two types of components: Buttons and Select Menus.
Purplet Message Component definitions define three things at once:
- The type definition of a "context" object, which allows you to pass a small amount of state between messages.
- A function that converts that context object into a message component object (`template`).
- A function that handles incoming interactions with the component (`handle`).
A basic example defining a button that is used with two different contexts, handled by the same function:
```ts title='src/features/message-component.ts'
// An interface describing the context object, which must be JSON-serializable.
export interface SampleContext {
name: string;
}
export const myButton = $buttonComponent({
// A function converting context -> component.
template(ctx: SampleContext) {
return new ButtonBuilder() //
.setLabel(`Button for ${ctx.name}`)
.setStyle(ButtonStyle.Secondary);
},
// A function handling interactions with the component, receiving the context object.
handle(ctx: SampleContext) {
this.showMessage(`You clicked the button for ${ctx.name}`);
},
});
export const testCommand = $slashCommand({
name: 'test',
description: 'Test command',
handle() {
// `myButton.create` is a function that takes the context object, and returns a component.
// We use `MessageComponentBuilder` to quickly build the UI layout.
this.showMessage({
components: new MessageComponentBuilder()
.addInline(myButton.create({ name: 'Dave' }))
.addInline(myButton.create({ name: 'Alice' }))
.toJSON(),
});
},
});
```
:::note
The way Component Context is implemented is by cramming the data into the component's `custom_id` field. With other data that purplet uses to store, you can store around 75 bytes of your own data, which is serialized as compactly as possible using [@purplet/serialize](https://github.com/CRBT-Team/Purplet/tree/main/packages/serialize).
:::
:::note
This page uses `ButtonBuilder` from `@discordjs/builders`, though we plan to create our own version of this class. See [this issue](https://github.com/CRBT-Team/Purplet/issues/23) for more details.
:::
## Render Props
Since the size of the context object is limited, you can use the `renderProps` property to pass additional data to the component that is only used for rendering. It is simply a second argument to the `create` function.
```ts
export const myButton = $buttonComponent({
template(ctx: SampleContext, renderProps: { style: ButtonStyle }) {
return new ButtonBuilder() //
.setLabel(`Button for ${ctx.name}`)
.setStyle(renderProps.style);
},
handle(ctx: SampleContext) {
this.showMessage(`You clicked the button for ${ctx.name}`);
},
});
// In a command
myButton.create({ name: 'Clement' }, { style: ButtonStyle.Primary });
```
## Contextless Components
While useful, the context system may not always be needed (eg. a static button). In this case, you can use the `component` property of the message to define a component without using a function.
```ts
export const myButton = $buttonComponent({
component: new ButtonBuilder() //
.setLabel('Button')
.setStyle(ButtonStyle.Secondary),
handle() {
this.showMessage('You clicked the button');
},
});
// `myButton.create()` will return the component passed in, but with `custom_id` set properly.
```
## Other Components
Buttons were used to illustrate the basics of the component system. Other components are also available, and their differences are shown below:
### Select Menus
Select Menus function exactly the same as Buttons, except they pass a `values` property to the context with the selected values.
```ts
export const mySelect = $selectMenuComponent({
template(ctx: SampleContext) {
return new SelectMenuBuilder() //
.setPlaceholder(`Select for ${ctx.name}`)
.setOptions([
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
]);
},
handle(context) {
this.showMessage(
`You selected ${context.values.join(' and ')} on the menu for ${context.name}`
);
},
});
```