Skip to main content

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
stores
Array<Store>
required
Array of Discord stores to subscribe to
callback
Function
required
Function that returns the state from the stores
deps
Array<any>
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
pluginName
string
required
Name of the plugin (used for data namespacing)
key
string
required
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.