Handle Commands UI

To show the command UI in the message input, set up the middlewares in MessageComposer using setupCommandUIMiddlewares:

Command UI

Best Practices

  • Register command middlewares once during client setup to avoid duplicates.
  • Keep custom command UI minimal so typing remains fast and predictable.
  • Use useStateStore selectors to avoid re-renders on unrelated composer state.
  • Always restore the composer state when closing the command UI.
  • Provide a clear escape path (e.g., close button or back gesture).
import { useEffect } from "react";
import { setupCommandUIMiddlewares } from "stream-chat-react-native";

const App = () => {
  useEffect(() => {
    if (!chatClient) {
      return;
    }
    chatClient.setMessageComposerSetupFunction(({ composer }) => {
      setupCommandUIMiddlewares(composer);
    });
  }, [chatClient]);
};

This adds four middlewares to MessageComposer:

  • createActiveCommandGuardMiddleware(stream-io/text-composer/active-command-guard) - Prevents new triggers when a command is active.
  • createCommandStringExtractionMiddleware(stream-io/text-composer/command-string-extraction) - Extracts the command string (for example, /ban useruser).
  • createCommandInjectionMiddleware(stream-io/message-composer-middleware/command-string-injection) - This injects the command string into the message composer state.
  • createDraftCommandInjectionMiddleware(stream-io/message-composer-middleware/draft-command-string-injection) - This injects the command string into the draft state.

All of the above middlewares are exported from stream-chat.

The command state in TextComposer is set via createCommandsMiddleware from stream-chat.

Customizing Command UI

To show a custom command UI, pass a custom component via the CommandInput prop on Channel.

import { Button, Text, View } from "react-native";
import {
  AutoCompleteInput,
  Channel,
  useMessageComposer,
  useStateStore,
  useMessageInputContext,
} from "stream-chat-react-native";

import { TextComposerState } from "stream-chat";

const textComposerStateSelector = (state: TextComposerState) => ({
  command: state.command,
});

const CustomInputCommand = () => {
  const messageComposer = useMessageComposer();
  const { textComposer } = messageComposer;
  const { command } = useStateStore(
    textComposer.state,
    textComposerStateSelector,
  );

  return (
    <View>
      <Text style={{ textAlign: "center" }}>{command?.name}</Text>
      <AutoCompleteInput />
      <Button
        onPress={() => {
          messageComposer.textComposer.setCommand(null);
          messageComposer?.restore();
        }}
        title="Close"
      />
    </View>
  );
};

<Channel channel={channel} CommandInput={CustomInputCommand}>
  {/* The underlying components */}
</Channel>;

The CommandInput component is only rendered when the command state is set in the message composer. Make sure you complete the above middlewares setup to make it work.