Documentation Index
Fetch the complete documentation index at: https://mintlify.com/betterdiscord/betterdiscord/llms.txt
Use this file to discover all available pages before exploring further.
BdApi.Hooks provides React hooks for use in BetterDiscord plugins. These hooks enable reactive updates and state management in plugin UIs.
Available hooks
useStateFromStores
A hook for subscribing to Discord stores and automatically re-rendering when they update.
BdApi.Hooks.useStateFromStores(
stores: Array<Store>,
callback: () => any,
deps?: Array<any>
): any
Array of Discord stores to subscribe to
Function that returns the state from the stores
Optional dependency array (similar to useEffect)
Returns: The value returned by the callback function
Example
const MyComponent = () => {
const currentUser = BdApi.Hooks.useStateFromStores(
[BdApi.Webpack.Stores.UserStore],
() => BdApi.Webpack.Stores.UserStore.getCurrentUser()
);
return (
<div>
<p>Logged in as: {currentUser?.username}</p>
</div>
);
};
useForceUpdate
A hook that returns a function to force a component to re-render.
BdApi.Hooks.useForceUpdate(): () => void
Returns: Function - A function that, when called, forces the component to re-render
Example
const MyComponent = () => {
const forceUpdate = BdApi.Hooks.useForceUpdate();
let counter = 0;
const increment = () => {
counter++;
forceUpdate(); // Force re-render to show updated counter
};
return (
<div>
<p>Count: {counter}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
useData
A hook for using plugin data storage with automatic re-rendering when data changes.
BdApi.Hooks.useData<T>(pluginName: string, key: string): T
Name of the plugin (used for data namespacing)
Key for the data to retrieve
Returns: T - The stored data value
Example
const SettingsPanel = () => {
const settings = BdApi.Hooks.useData("MyPlugin", "settings");
const updateSetting = (key, value) => {
const newSettings = { ...settings, [key]: value };
BdApi.Data.save("MyPlugin", "settings", newSettings);
};
return (
<div>
<label>
<input
type="checkbox"
checked={settings?.enabled ?? false}
onChange={e => updateSetting("enabled", e.target.checked)}
/>
Enable feature
</label>
</div>
);
};
Complete examples
Settings panel with reactive data
const SettingsPanel = () => {
const settings = BdApi.Hooks.useData("MyPlugin", "settings");
const forceUpdate = BdApi.Hooks.useForceUpdate();
const updateSetting = (key, value) => {
settings[key] = value;
BdApi.Data.save("MyPlugin", "settings", settings);
forceUpdate();
};
return (
<div className="settings-panel">
<h2>MyPlugin Settings</h2>
<div className="setting-item">
<label>
<input
type="checkbox"
checked={settings?.notifications ?? true}
onChange={e => updateSetting("notifications", e.target.checked)}
/>
Enable notifications
</label>
</div>
<div className="setting-item">
<label>
Theme:
<select
value={settings?.theme ?? "dark"}
onChange={e => updateSetting("theme", e.target.value)}
>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="auto">Auto</option>
</select>
</label>
</div>
</div>
);
};
module.exports = class MyPlugin {
getSettingsPanel() {
return SettingsPanel;
}
};
User status indicator
const UserStatus = ({ userId }) => {
const user = BdApi.Hooks.useStateFromStores(
[BdApi.Webpack.Stores.UserStore],
() => BdApi.Webpack.Stores.UserStore.getUser(userId)
);
const status = BdApi.Hooks.useStateFromStores(
[BdApi.Webpack.Stores.PresenceStore],
() => BdApi.Webpack.Stores.PresenceStore.getStatus(userId)
);
if (!user) return null;
return (
<div className="user-status">
<img src={user.getAvatarURL()} alt={user.username} />
<div>
<div className="username">{user.username}</div>
<div className={`status ${status}`}>{status}</div>
</div>
</div>
);
};
Channel message counter
const MessageCounter = () => {
const selectedChannelId = BdApi.Hooks.useStateFromStores(
[BdApi.Webpack.Stores.SelectedChannelStore],
() => BdApi.Webpack.Stores.SelectedChannelStore.getChannelId()
);
const messages = BdApi.Hooks.useStateFromStores(
[BdApi.Webpack.Stores.MessageStore],
() => BdApi.Webpack.Stores.MessageStore.getMessages(selectedChannelId),
[selectedChannelId]
);
const messageCount = messages ? messages.length : 0;
return (
<div className="message-counter">
<p>Messages in this channel: {messageCount}</p>
</div>
);
};
Combined hooks example
const PluginDashboard = () => {
const forceUpdate = BdApi.Hooks.useForceUpdate();
const config = BdApi.Hooks.useData("MyPlugin", "config");
const currentUser = BdApi.Hooks.useStateFromStores(
[BdApi.Webpack.Stores.UserStore],
() => BdApi.Webpack.Stores.UserStore.getCurrentUser()
);
const selectedChannel = BdApi.Hooks.useStateFromStores(
[BdApi.Webpack.Stores.SelectedChannelStore],
() => {
const channelId = BdApi.Webpack.Stores.SelectedChannelStore.getChannelId();
return BdApi.Webpack.Stores.ChannelStore.getChannel(channelId);
}
);
return (
<div className="plugin-dashboard">
<h2>Dashboard</h2>
<p>User: {currentUser?.username}</p>
<p>Channel: {selectedChannel?.name || "None"}</p>
<p>Config: {JSON.stringify(config)}</p>
<button onClick={forceUpdate}>Refresh</button>
</div>
);
};
Best practices
Always specify dependencies when using useStateFromStores if your callback depends on external values. This ensures the hook re-subscribes when dependencies change.
Avoid calling forceUpdate excessively, as it can impact performance. Prefer using useStateFromStores or useData for reactive updates when possible.
When using useData, make sure to save data changes using BdApi.Data.save() to persist them and trigger re-renders in components using the same data.