Using Signals
Signals are a way to add states and basic reactivity to your commands. It is similar to SolidJS signals.
Creating a signal
Creating a signal is simple, you just need to call the createSignal
function from commandkit
. A signal in commandkit is a function that returns the current value of the signal. It also has a set
function that can be used to update the value of the signal. The dispose
function can be used to dispose the signal and all its subscribers.
signal.js
const { createSignal } = require('commandkit');
// createValue accepts one param (the initial value)
const [value, setValue, disposeValueSubscribers] = createSignal(0);
// Log the initial value
console.log(value()); // -> 0
// Update the value
setValue(1);
// Log the new value
console.log(value()); // -> 1
// Dispose the value's subscribers
disposeValueSubscribers();
Handling side effects
You can also handle side effects with signals, by using the createEffect
function. Side effects are functions that run every time the signal value changes.
signal.js
const { createSignal, createEffect } = require('commandkit');
const [value, setValue, disposeValueSubscribers] = createSignal(0);
// This will run every time the value changes
createEffect(() => {
console.log(`Current value is ${value()}`);
});
setValue(1); // Logs "Current value is 1"
setValue(2); // Logs "Current value is 2"
// Dispose the value's subscribers
disposeValueSubscribers();
// Logs nothing because subscribers were disposed
setValue(3);
Count command example
commands/counter.js
const {
createSignal,
createEffect,
ButtonKit
} = require('commandkit');
const { ButtonStyle, ActionRowBuilder } = require('discord.js');
exports.data = {
name: 'counter',
description: 'A simple counter command',
};
function getButtons() {
// Decrement button
const dec = new ButtonKit()
.setEmoji('➖')
.setStyle(ButtonStyle.Primary)
.setCustomId('decrement');
// Reset button
const reset = new ButtonKit()
.setEmoji('0️⃣')
.setStyle(ButtonStyle.Primary)
.setCustomId('reset');
// Increment button
const inc = new ButtonKit()
.setEmoji('➕')
.setStyle(ButtonStyle.Primary)
.setCustomId('increment');
// Disposal button
const trash = new ButtonKit()
.setEmoji('🗑️')
.setStyle(ButtonStyle.Danger)
.setCustomId('trash');
// Create an action row
const row = new ActionRowBuilder()
.addComponents(dec, reset, inc, trash);
return { dec, reset, inc, trash, row };
}
exports.run = async ({ interaction }) => {
// Create the signal & buttons
const [count, setCount, disposeCountSubscribers] = createSignal(0);
const { dec, reset, inc, trash, row } = getButtons();
// Temporary variable to hold button interactions
let inter;
// Send the initial message with the buttons
const message = await interaction.reply({
content: `Count is ${count()}`,
components: [row],
fetchReply: true,
});
// Now, we subscribe to count signal and update the message every time the count changes
createEffect(() => {
// Make sure to "always" call the value function inside createEffect, otherwise the subscription will not occur
const value = count();
// Now udate the original message
inter?.update(`Count is ${value}`);
});
// Handler to decrement the count
dec.onClick((interaction) => {
inter = interaction;
setCount((prev) => prev - 1);
}, { message });
// Handler to reset the count
reset.onClick((interaction) => {
inter = interaction;
setCount(0);
}, { message });
// Handler to increment the count
inc.onClick((interaction) => {
inter = interaction;
setCount((prev) => prev + 1);
}, { message });
// Disposal handler
trash.onClick(async (interaction) => {
const disposed = row.setComponents(
row.components.map((button) => {
// Remove the 'onClick' handler and disable the button
return button
.onClick(null)
.setDisabled(true);
}),
);
// Dispose the signal's subscribers
disposeCountSubscribers();
// And finally: acknowledge the interaction
await interaction.update({
content: 'Finished counting!',
components: [disposed],
});
}, { message });
}