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

src/channel-log/index.ts
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.

FunctionDoes
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

ActionDispatched when
MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETEA message is sent, edited or deleted
CHANNEL_SELECT, GUILD_SELECTThe user switches channel or server
TYPING_START, TYPING_STOPSomeone starts or stops typing
CONNECTION_OPENThe 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.

Next