Flux Stores
Read Discord's state and react to actions.
Read a Flux store for Discord's current state. Listen for a Flux action to run code when something happens.
Quick Start
import { flux } from '@unbound-app/api';
export function startChannelLog() {
flux.onAction('CHANNEL_SELECT', ({ channelId }) => {
logger.log('Channel selected:', channelId);
});
}Reference
Every function is on flux, from @unbound-app/api. The scope removes every listener and interceptor when the plugin stops.
| Function | Does |
|---|---|
flux.getStore(name, options?) | Returns the typed store, such as 'User' for UserStore. Lazy by default. Options: lazy, and short: false to match the full name |
flux.useStore(stores, selector, options?) | In a component, returns selector(stores). Re-renders only when it changes. Options: deps, isEqual |
flux.onAction(type, listener) | Calls listener with each action of type, after stores handle it. Returns a remover |
flux.interceptAction(type, interceptor) | Calls interceptor before Discord handles the action. It may change it, or return true to drop it. Returns a remover |
flux.onStoreChange(store, listener) | Calls listener after each change to store. Returns a remover |
flux.dispatch(action) | Sends an action through Discord's dispatcher. Resolves once stores handled it |
Typed stores: User, Guild, GuildMember, Channel, SelectedChannel, Typing, Relationship, UserProfile, GuildSettings. Any other name returns a base store with getName, addChangeListener, removeChangeListener and emitChange.
Reading a Store
flux.getStore is lazy and typed, so declare it at the top of a file. Prefer it to metro.stores, which has no types.
import { flux } from '@unbound-app/api';
const UserStore = flux.getStore('User');
export function currentUsername() {
return UserStore.getCurrentUser()?.username;
}In a Component
Use flux.useStore with the Flux store and a selector. The component re-renders only when the selector's result changes.
import { Discord } from '@unbound-app/api/metro/components';
import { flux } from '@unbound-app/api';
const UserStore = flux.getStore('User');
function Username() {
const username = flux.useStore(UserStore, (store) => store.getCurrentUser()?.username);
return <Discord.Text variant='text-md/medium'>{username}</Discord.Text>;
}For several stores, pass an array; the selector gets the same array. Put props the selector uses in deps.
import { flux } from '@unbound-app/api';
const ChannelStore = flux.getStore('Channel');
const UserStore = flux.getStore('User');
export function useChannelLabel(channelId: string) {
return flux.useStore(
[ChannelStore, UserStore],
([channels, users]) =>
`${channels.getChannel(channelId)?.name} as ${users.getCurrentUser()?.username}`,
{ deps: [channelId] },
);
}A selector returning a new object or array re-renders on every change. Wrap it in utils.react.useShallow to compare top-level fields.
import { flux, utils } from '@unbound-app/api';
const UserStore = flux.getStore('User');
export function useIdentity() {
return flux.useStore(
UserStore,
utils.react.useShallow((store) => ({
username: store.getCurrentUser()?.username,
avatar: store.getCurrentUser()?.avatar,
})),
);
}For another comparison, pass isEqual(old, new), returning true to skip the re-render. utils.react.shallow is the one useShallow uses. See flux in the SDK reference.
Common Actions
| Action | Dispatched when |
|---|---|
MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE | A message is sent, edited or deleted |
CHANNEL_SELECT, GUILD_SELECT | The user switches channel or server |
TYPING_START, TYPING_STOP | Someone starts or stops typing |
CONNECTION_OPEN | The client connects and stores fill. Use it for setup needing live state |
All but GUILD_SELECT have typed payloads. To type others, see flux in the SDK reference.