This is documentation for the release candidate Stream Chat React Native SDK v8. For the latest stable version, see the latest version (v7).

MessageComposer Class

The MessageComposer serves as a central orchestrator for message creation and editing. It:

  • Manages the state of message composition
  • Handles different types of content (text, attachments, polls, etc.)
  • Provides draft functionality for saving message progress
  • Supports message editing and quoting
  • Integrates with the Stream Chat API for message operations

We can initiate the composer instance as follows:

new MessageComposer({
  client, // StreamChat client instance
  compositionContext, // it is necessary to provide a Channel or Thread instance or LocalMessage object
  composition, // optional initial state like draft message or edited message
  config, // optional custom configuration
});

Each Channel and Thread instance has its own messageComposer attribute assigned on instantiation. In case we need to edit a message, we pass the LocalMessage object to compositionContext and composition constructor parameters.

MessageComposer is built using a modular architecture with specialized managers for different aspects of message composition:

  • TextComposer: Handles text input, commands, mentions, and text-related operations
  • AttachmentManager: Keeps message attachments state and manages file attachments uploads
  • LinkPreviewsManager: Processes and manages link previews in messages
  • PollComposer: Handles poll composition and creation
  • CustomDataManager: Allows for custom message and non-message related data

Each manager operates independently and keeps its own dedicated state. MessageComposer orchestrates their interactions.

The following are the general MessageComposer features:

  • State Management: Uses a reactive state management system to track changes
  • Middleware Support: Allows for custom processing of messages through middleware
  • Draft System: Supports saving and restoring message drafts
  • Rich Content: Handles various types of content including text, attachments, polls, and link previews
  • Event System: Provides event subscriptions for real-time updates
  • Configurable: Highly configurable through the MessageComposerConfig interface

StreamChat.setMessageComposerSetupFunction

One MessageComposer setup function per client, ideally you’d setup this function only once (or each time a specific dependency changes).

import { defaultTextComposerMiddlewares } from "stream-chat";

const chatClient = useCreateChatClient({
  apiKey,
  tokenOrProvider: userToken,
  userData: { id: userId, language: "en" },
});

const [emojisEnabled, setEmojisEnabled] = useState(false);

useEffect(() => {
  chatClient.setMessageComposerSetupFunction(({ composer }) => {
    if (composer.contextType === "channel") {
      composer.textComposer.insert(
        defaultTextComposerMiddlewares.map(composer.channel),
      );

      if (emojisEnabled) {
        composer.textComposer.middlewareExecutor.insert({
          middleware: [
            createTextComposerEmojiMiddleware(
              SearchIndex,
            ) as TextComposerMiddleware,
          ],
          position: { after: "stream-io/text-composer/mentions-middleware" },
          unique: true,
        });
      }
    }
  });
}, [chatClient, emojisEnabled]);

Message Composer Configuration

Configuration

The MessageComposer can be configured at different levels, from global client-wide settings to individual manager configurations.

Configuration Structure

The configuration is defined by the MessageComposerConfig interface:

type MessageComposerConfig = {
  drafts: {
    enabled: boolean; // Enables server-side draft functionality. Disabled by default.
  };
  attachments: {
    /**
     * Function to filter out files before being uploaded. The default forwards without filtering.
     */
    fileUploadFilter: (file: FileLike) => boolean;
    /**
     * Maximum file upload attachments per message. Default is 10.
     */
    maxNumberOfFilesPerMessage: number;
    /**
     * Allowed file types.
     * Expected are unique file type specifiers
     * (https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/accept#unique_file_type_specifiers).
     * The default is an empty array - all files accepted.
     */
    acceptedFiles: string[];
    /**
     * Custom upload request (and possibly logic). For example upload to custom CDN.
     */
    doUploadRequest?: (
      file: FileLike,
    ) => Promise<{ file: string; thumb_url?: string }>;
  };
  linkPreviews: {
    /**
     * Enables link previews. Disabled by default.
     */
    enabled: boolean;
    /**
     * Debounce time for URL detection and enrichment. By default 1500ms.
     */
    debounceURLEnrichmentMs: number;
    /**
     * Custom URL detection function. The default is linkifyjs.find.
     */
    findURLFn: (text: string) => string[];
    /**
     * Custom logic to be invoked when a specific link preview is dismissed
     */
    onLinkPreviewDismissed?: (linkPreview: LinkPreview) => void;
  };
  text: {
    /**
     * Enables text input. Enabled by default.
     */
    enabled: boolean;
    /**
     * Enable sending typing events which are used to display typing indicators. Enabled by default.
     */
    publishTypingEvents: boolean;
    /**
     * Initial text value if draft or existing message is not available.
     */
    defaultValue?: string;
    /**
     * The message text will be trimmed to maxLengthOnEdit on text change or suggestion selection.
     */
    maxLengthOnEdit?: number;
    /**
     * The message text will be trimmed to maxLengthOnSend during the final message composition before being sent.
     */
    maxLengthOnSend?: number;
  };
};

Configuration Methods

1. Global Configuration

Use client.setMessageComposerSetupFunction to configure all MessageComposer instances:

client.setMessageComposerSetupFunction(({ composer }) => {
  composer.updateConfig({
    drafts: { enabled: true },
    attachments: {
      maxNumberOfFilesPerMessage: 5,
      acceptedFiles: ["image/*", "video/*"],
    },
  });
});

This configuration will be applied also to all new MessageComposer instances (for example created for editing a message).

2. Instance-Specific Configuration

Configure a specific MessageComposer instance using updateConfig:

messageComposer.updateConfig({
  text: {
    enabled: true,
    publishTypingEvents: false,
  },
});

3. Manager-Level Configuration

Each manager can be configured individually through its properties:

// Configure TextComposer
messageComposer.textComposer.defaultValue = "Dear Customer Service";

// Configure AttachmentManager
messageComposer.attachmentManager.acceptedFiles = ["image/*", "video/*"];

// Configure LinkPreviewsManager
messageComposer.linkPreviewsManager.enabled = true;

// General message composition parameters. Applies to drafts.
messageComposer.updateConfig({ drafts: enabled });

Configuring the message id generation

We can provide a custom message id generator function by overriding the MessageComposer.generateId static method:

messageComposer.generateId = customGenerator;

Message Composer Context Type

Each MessageComposer has a contextType getter which gets evaluated from the compositionContext value provided at construction time. A composer can have a context type of channel, thread, legacy_thread or message.

You might not need this information but it’s there when you need it; for example when you’re deciding what middlewares to apply to only channel-based or thread-based composers.

Type legacy_thread is a simple message object extended with legacyThreadId property whose value equals to that message’s id.

Accessing the Corresponding MessageComposer instance

The useMessageComposer hook provides access to the appropriate MessageComposer instance based on the current context (channel, thread, message). It automatically handles different composition scenarios:

  • Composing a new message in a channel
  • Replying in a thread
  • Editing an existing message
  • Replying in a legacy thread that does not rely on Thread instance

The usage:

import { useMessageComposer } from "stream-chat-react-native";

const MyComponent = () => {
  const messageComposer = useMessageComposer();
  const handleSend = async () => {
    // Access composer features
    const { localMessage, message, sendOptions } =
      await messageComposer.compose();
    // Handle the composed message
    // ...
  };

  return <button onClick={handleSend}>Send</button>;
};

The hook automatically resolves the correct MessageComposer instance based on the following hierarchy:

  1. Editing a message: If a message is being edited, a new composer instance is created for that specific message
  2. Thread reply: If in a thread context, it uses the thread’s composer instance
  3. Legacy thread reply: If replying to a message in a context that does not rely on Thread instance that already contains its MessageComposer instance.
  4. Channel message: Falls back to the channel’s composer instance

It automatically does the following:

  • Registers the necessary subscriptions when the composer is mounted
  • Cleans up subscriptions when the component unmounts

The hook implements caching for composer instances in certain contexts:

  • Editing a message
  • Replying in a legacy thread

This ensures that the same composer instance is reused when returning to these contexts, preserving the composition state.

Message Composer Utility Hooks

useAttachmentManagerState

const {
  attachments,
  availableUploadSlots,
  blockedUploadsCount,
  failedUploadsCount,
  isUploadEnabled,
  pendingUploadsCount,
  successfulUploadsCount,
  uploadsInProgressCount,
} = useAttachmentManagerState();

Use this hook when you need to:

  • Access the attachment data
  • Track attachment upload progress and status
  • Show upload progress indicators
  • Enable/disable UI elements based on upload state
  • Display attachment counts and limits

useCanCreatePoll

const canCreatePoll = useCanCreatePoll();

Use this hook to:

  • Determine if a poll can be created with the current state
  • Enable/disable the send button

useMessageComposerHasSendableData

const hasSendableData = useMessageComposerHasSendableData();

Use this hook to:

  • Enable/disable the send button

© Getstream.io, Inc. All Rights Reserved.