MessageComposer API
This page lists the API of MessageComposer in stream-chat and of the parts it's built from: the text composer, the attachment manager, the link previews manager, the location composer, the poll composer and the custom data manager.
What a composer does, and how sending, uploads, drafts and editing work, is explained on Message composer. Its middleware chains are on Composer middleware, and its settings on Configuration tree.
Best practices
- Subscribe to a part's
stateto follow it. Getters such astextComposer.textorcomposer.quotedMessagereturn the value at the moment you read them, and don't tell you when it changes. - Change state through the methods below. Writing to a store directly skips the checks and side effects the methods run, such as cancelling uploads or applying a command.
- Check what
MessageComposer.send()andMessageComposer.update()return. They never throw: they resolve to'sent','nothing-to-send'or'failed'. - Configure every composer through
client.config. Settings made on one composer withMessageComposer.updateConfig()or a setter stay on that composer, and uploads always use the channel composer's upload settings. - Change behaviour with middleware. Adding, replacing or removing a step of a chain is the supported way to change how text, uploads and messages are processed.
- Inside a thread, use the thread's composer,
thread.messageComposer, not the channel's.
const unsubscribe = composer.textComposer.state.subscribeWithSelector(
({ text }) => ({ text }),
({ text }) => console.log(`${text.length} characters`),
);MessageComposer class architecture
A MessageComposer coordinates its parts. Each part keeps its data in a store of its own:
graph TD
MC[MessageComposer] --> TC[textComposer]
MC --> AM[attachmentManager]
MC --> LPM[linkPreviewsManager]
MC --> LC[locationComposer]
MC --> PC[pollComposer]
MC --> CDM[customDataManager]| Part | Class | Holds |
|---|---|---|
textComposer |
TextComposer |
The text, the cursor, mentions, the active command, suggestions and typing events. |
attachmentManager |
AttachmentManager |
Attachments other than link previews, and their uploads. |
linkPreviewsManager |
LinkPreviewsManager |
Previews for the links in the text. |
locationComposer |
LocationComposer |
A location to share. |
pollComposer |
PollComposer |
The poll being created. |
customDataManager |
CustomDataManager |
Data of your own, sent with the message or kept in the composer. |
Getting a composer
| Composer | Where it comes from | contextType |
|---|---|---|
| Channel | channel.messageComposer |
'channel' |
| Thread | thread.messageComposer |
'thread' |
| Edit | new MessageComposer(...) |
'message' |
The channel and thread composers are created with their channel or thread. Build a composer yourself only to edit a message outside them:
import { MessageComposer } from "stream-chat";
const editComposer = new MessageComposer({
client,
compositionContext: message,
composition: message,
});The constructor takes MessageComposerOptions:
| Option | Type | What it is |
|---|---|---|
client |
StreamChat |
The client. |
compositionContext |
Channel, Thread or LocalMessage |
What the composer belongs to. A message must carry its cid, or the constructor throws. |
composition |
DraftResponse, MessageResponse or LocalMessage |
Optional. The content to start with: a draft, or the message to edit. |
config |
DeepPartial<MessageComposerConfig> |
Optional. Settings for this composer, above client.config and below MessageComposer.updateConfig(). |
A composer starts with an empty state, with a draft, or with a message to edit:
- Empty. The composer has a new
id, which becomes the id of the message it sends. - A draft. The content is loaded, and
draftIdis the draft's id. The message gets a newid. - A message to edit. The content is loaded,
editedMessageis the message, andidis the message's id. Drafts aren't saved and commands can't be used while editing.
Composer state
| Store | Type | What it holds |
|---|---|---|
state |
StateStore<MessageComposerState> |
The composer's own fields, in the table below. |
editingAuditState |
StateStore<EditingAuditState> |
lastChange: { draftUpdate, stateUpdate }: when the content and the draft last changed, in nanoseconds. |
configState |
StateStore<MessageComposerConfig> |
The settings in effect. config reads the same value once. |
state field |
Type | What it holds |
|---|---|---|
id |
string |
The id of the message the composer will send. |
draftId |
string or null |
The id of the loaded or saved draft. |
pollId |
string or null |
The poll created with MessageComposer.createPoll(), which the next message carries. |
quotedMessage |
LocalMessage or null |
The message being replied to. |
showReplyInChannel |
boolean |
Whether a thread reply is also shown in the channel. |
editedMessage |
LocalMessage or null |
The message being edited, as it was when the edit began. MessageComposer.restore() goes back to it. |
Getters
The getters of MessageComposer return the value at the moment you read them, and don't tell you when it changes.
| Getter | Returns |
|---|---|
id, draftId, pollId |
The state fields of those names. |
quotedMessage |
The quotedMessage field. |
showReplyInChannel |
The showReplyInChannel field. |
editedMessage |
The edited message, or undefined. Has a setter, which writes the field. |
lastChange |
editingAuditState.lastChange. |
lastChangeOriginIsLocal |
Whether the content changed here since it was loaded, or was written from scratch. |
config |
The settings in effect, after the server's limits. |
requestedConfig |
The settings asked for, before the server's limits. |
contextType |
'channel', 'thread' or 'message'. |
tag |
contextType and the context's id, such as channel_general. |
threadId |
The thread's id for a thread composer, the parent id for an edit of a reply, otherwise null. |
channel |
The composer's channel. For an edit composer, the channel from the message's cid. |
client |
The channel's client. |
compositionContext |
What the constructor received as compositionContext. |
defaultSubmitTarget |
Where MessageComposer.send() and MessageComposer.update() go: the thread for a thread composer, the reply's thread when client.threads has it, else the channel. |
hasSendableData |
Whether there is something to send. A running upload blocks it, unless attachments.pendingUploadsEnabled is on. |
contentIsEmpty |
Whether text, attachments, poll and location are all empty. |
compositionIsEmpty |
contentIsEmpty, and no quoted message either. |
allowsPendingUploads |
attachments.pendingUploadsEnabled. |
retainsCompositionOnSubmit |
What retainCompositionOnSubmit answers for the current content. |
isCommandSendable |
Whether the active command, if there is one, can be sent. |
Methods
Sending
| Method | Returns | What it does |
|---|---|---|
send() |
Promise<MessageComposerSubmitResult> |
Composes the message, clears the composer unless retainCompositionOnSubmit keeps the content, and sends it through the target's message operations. Never throws. |
update() |
Promise<MessageComposerSubmitResult> |
The same for an edit: saves editedMessage with the new content. Never throws. |
compose() |
Promise<MessageComposerMiddlewareState | undefined> |
Runs the composition chain and returns { message, localMessage, sendOptions }, or undefined when a step discarded it. Sends nothing. |
sendLocation() |
Promise<void> |
Sends the location in locationComposer as a message of its own. Does nothing in a thread or without a valid location. Throws when the request fails. |
MessageComposerSubmitResult is 'sent', 'nothing-to-send' or 'failed'. Message composer explains each one.
const result = await composer.send();
if (result === "failed") {
// The message is in the list as failed, and the composer is already empty.
}State
| Method | What it does |
|---|---|
initState({ composition }?) |
Loads a draft or a message into every part, or empties them without composition. |
initStateFromChannelResponse(response) |
Loads the draft a channel query returned, or clears a draft that's gone. |
clear() |
Empties the composer, the quoted message included, and leaves editing. |
restore() |
Puts editedMessage back into the composer, or clears it when nothing is being edited. |
refreshId() |
Gives the composer a new id. |
setQuotedMessage(message | null) |
Sets or removes the quoted message. A command the quote disables is cleared. |
setEditedMessage(message | null | undefined) |
Sets the edited message without loading it, and clears the active command. |
toggleShowReplyInChannel() |
Flips showReplyInChannel. |
Drafts. MessageComposer.createDraft(), MessageComposer.deleteDraft() and MessageComposer.getDraft() do nothing while drafts.enabled is off or a message is being edited. The first two also do nothing for a channel that has no id yet.
| Method | Returns | What it does |
|---|---|---|
createDraft() |
Promise<void> |
Runs the draft chain and saves the result as the draft. Skips an empty draft or one that didn't change. |
deleteDraft() |
Promise<void> |
Deletes the saved draft. |
getDraft() |
Promise<void> |
Loads the channel's or thread's draft into the composer. |
composeDraft() |
Promise<{ draft } | undefined> |
Runs the draft chain only. |
Polls
| Method | Returns | What it does |
|---|---|---|
createPoll() |
Promise<void> |
Creates the poll from pollComposer on the server and sets pollId. Throws when the request fails, after publishing an api:poll:create:failed notification. |
Commands
| Method | Returns | What it does |
|---|---|---|
getCommandDisabledReason(command) |
'editing', 'quoted_message' or undefined |
Why a command can't be used now: while editing, or a moderation command with a quote. |
isCommandDisabled(command) |
boolean |
Whether getCommandDisabledReason returns a reason. |
validateCommandSendability(command, text?) |
CommandSendability |
Asks commands.sendValidator whether the command can be sent with the text. |
Configuration
| Method | What it does |
|---|---|
updateConfig(patch) |
Changes this composer's settings. The change is kept when the settings are rebuilt, except by client.config.reset(). |
initializeConfig() |
Rebuilds the settings from client.config and drops updateConfig() changes. client.config.reset() calls it. |
Effects and snapshots. The command chips use these, and Effect handlers explains them.
| Method | What it does |
|---|---|
registerEffectHandler(type, handler) |
Runs handler(effect, composer) for effects of that type. Replaces the handler for a built-in type. |
applyEffects(effects) |
Runs the handlers for a list of effects. |
getSnapshot() |
Returns the state of every part. |
restoreSnapshot(snapshot) |
Puts a snapshot back into every part. |
captureSnapshot(snapshot?) |
Keeps a snapshot, unless one is kept already. |
popSnapshot() |
Returns the kept snapshot and forgets it. |
clearSnapshots() |
Forgets the kept snapshot. |
Subscriptions
| Method | What it does |
|---|---|
registerSubscriptions() |
Starts the composer's event handling and its setup function, and returns a function that stops them. The UI layer calls it while it shows the composer. |
unregisterSubscriptions() |
Stops them. |
registerDraftEventSubscriptions() |
Starts following draft.updated and draft.deleted, and returns a function that stops. The composer calls it while drafts are on. |
Static members
| Member | What it does |
|---|---|
MessageComposer.generateId() |
Returns a new message id. |
MessageComposer.evaluateContextType(context) |
Returns 'channel', 'thread' or 'message' for a context. |
MessageComposer.constructTag(context) |
Returns the tag a composer for that context would have. |
Events
While its subscriptions run, a composer reacts to these:
| Event | What happens |
|---|---|
message.updated, reaction.new, reaction.updated, reaction.deleted |
For the message being edited, the composer reloads it from the event, and unsaved changes are lost. For the quoted message, the quote is updated. |
message.deleted |
For the message being edited, the composer is cleared. For the quoted message, the quote is removed. |
draft.updated, draft.deleted |
With drafts on and no edit in progress, the composer loads the draft in the event, or clears itself when the draft is deleted. |
The composer also rebuilds its settings when client.config or its setup function changes and when the channel's server settings arrive. When the content becomes empty, it deletes the draft. When the text changes, it looks for links to preview.
Text composition with suggestions
composer.textComposer is a TextComposer. Its state holds:
| Field | Type | What it holds |
|---|---|---|
text |
string |
The text. |
selection |
{ start, end } |
The cursor, or the selected range. |
mentions |
MentionEntity[] |
The mentions in the text: users, @channel, @here, roles and user groups. Mentions no longer in the text are left out of the message. |
mentionedUsers |
UserResponse[] |
Deprecated. The user mentions only. Use mentions. |
command |
Command or null |
The active slash command, kept apart from the text. |
suggestions |
Suggestions or undefined |
The open suggestions: { query, searchSource, trigger }. |
typing |
Record<string, Event> |
The users typing, by user id: each one's latest typing event. It can include the current user. Kept on the channel's composer. |
A MentionEntity has an id and a mentionType: 'user' (with the user's fields), 'channel', 'here', 'role' or 'user_group'. Suggestions explains how mentions and commands are found.
Getters and setters
text, selection, mentions, mentionedUsers, command, suggestions and typing read the state. textIsEmpty says whether the text is empty or only Markdown markers. config is composer.config.text, channel the composer's channel, and middlewareExecutor the text chain.
These read the composer's text settings and write them with MessageComposer.updateConfig(), for this composer only:
| Getter and setter | Setting |
|---|---|
enabled |
text.enabled |
defaultValue |
text.defaultValue |
maxLengthOnEdit |
text.maxLengthOnEdit |
maxLengthOnSend |
text.maxLengthOnSend |
publishTypingEvents |
text.publishTypingEvents |
Methods
| Method | What it does |
|---|---|
handleChange({ text, selection }) |
The input path. Runs the text chain, which cuts the text to maxLengthOnEdit and finds mentions and commands, then sends a typing event. |
handleSelect(suggestion) |
Runs the text chain for a picked suggestion, which inserts the mention or sets the command. |
insertText({ text, selection? }) |
Inserts at selection, or at the cursor, and goes through handleChange(). |
wrapSelection({ head?, tail?, selection? }) |
Puts head before and tail after the selection, for example ** for bold. Runs no middleware. |
setText(text) |
Sets the text without running any middleware. |
setSelection(selection) |
Sets the cursor or the selected range. |
setCommand(command | null) |
Makes a command active, unless the composer disables it. null clears it. |
clearCommand() |
Clears the active command and restores what the composer held before it was picked. |
upsertMentionEntity(entity) |
Adds a mention, or replaces the one with the same mentionType and id. |
getMentionEntity(mentionType, id) |
Returns a mention, or undefined. |
removeMentionEntity(mentionType, id) |
Removes a mention. |
setMentions(entities) |
Replaces all mentions. |
setMentionedUsers(users) |
Replaces the user mentions and keeps the others. |
setSuggestions(suggestions) |
Opens suggestions: { query, searchSource, trigger }. |
closeSuggestions() |
Closes them. |
setTyping(typing), setTypingEvent(userId, event), removeTypingEvent(userId) |
Write the typing field. The channel calls setTyping() on typing events. |
initState({ message }?) |
Loads a message's text and mentions, or the default text without one. |
getSnapshot(), restoreSnapshot(snapshot) |
Read and put back the whole state. |
TextComposer.upsertMentionedUser(user), TextComposer.getMentionedUser(userId) and TextComposer.removeMentionedUser(userId) are deprecated. Use the mention-entity methods with mentionType: 'user'.
Pass a selection to TextComposer.insertText() and TextComposer.wrapSelection(), or keep the cursor current with TextComposer.setSelection(): without one they use textComposer.selection.
textIsEmpty(text), exported from stream-chat, is the check textIsEmpty uses.
Attachment management
composer.attachmentManager is an AttachmentManager. Its state has one field, attachments, a LocalAttachment[].
Every local attachment has a localMetadata object, which is removed before the message is sent:
| Field | What it holds |
|---|---|
id |
The attachment's local id, which the methods below take. |
file |
The File or FileReference to upload. |
uploadState |
'pending', 'uploading', 'finished', 'failed' or 'blocked'. |
uploadProgress |
0 to 100 while uploading, when attachments.trackUploadProgress is on. |
uploadConfirmationPending |
Every byte is sent and the server hasn't answered yet. |
uploadPermissionCheck |
{ uploadBlocked, reason? }: why a file was blocked by the app's upload settings. |
previewUri |
A local URL for previewing the file. |
An attachment loaded from a draft or an edited message has only an id and, for an uploaded file, uploadState: 'finished'.
Getters
| Getter | Returns |
|---|---|
attachments |
The attachments. |
attachmentsById |
The attachments by local id. |
isUploadEnabled |
Whether a file can be added now: attachments are enabled, a slot is free, and the user has upload-file unless customCdn is on. |
availableUploadSlots, hasAvailableUploadSlots |
How many more files fit under maxNumberOfFilesPerMessage, and whether any do. |
successfulUploads |
The finished uploads. |
successfulUploadsCount, uploadsInProgressCount, pendingUploadsCount, failedUploadsCount, blockedUploadsCount |
The number of attachments in each upload state. |
hasUploadPermission |
Whether the user has the upload-file capability in the channel. |
usesStreamStorage |
true unless attachments.customCdn is on. |
hasCustomDoUploadRequest |
Whether attachments.doUploadRequest is set. |
config |
composer.config.attachments. |
client, channel |
The composer's client and channel. |
acceptedFiles, maxNumberOfFilesPerMessage and the deprecated fileUploadFilter are getters and setters for the settings of those names, on this composer only. preUploadMiddlewareExecutor and postUploadMiddlewareExecutor are the upload chains.
Methods
| Method | Returns | What it does |
|---|---|---|
uploadFiles(files) |
Promise<LocalUploadAttachment[] | undefined> |
Adds and uploads as many files as there are free slots. Does nothing when isUploadEnabled is false. |
uploadFile(file) |
Promise<LocalUploadAttachment> |
Adds and uploads one file, through the pre-upload and post-upload chains. Doesn't check isUploadEnabled. |
upsertAttachments(attachments) |
void |
Adds attachments, or merges them into the ones with the same local id. |
updateAttachment(attachment) |
void |
Merges changes into an existing attachment. |
removeAttachments(localIds) |
void |
Removes attachments and cancels their uploads. |
clearAttachments() |
void |
Removes every attachment. |
setAttachments(attachments) |
void |
Replaces the list without any checks. |
getAttachmentIndex(localId) |
number |
The attachment's position, or -1. |
getUploadsByState(state) |
LocalAttachment[] |
The attachments in one upload state. |
cancelAttachmentUploads(attachments?) |
void |
Cancels the uploads of the given attachments, or of all. |
setCustomUploadFn(fn) |
void |
Sets attachments.doUploadRequest for this composer. |
doUploadRequest(file, options?) |
Promise<MinimumUploadRequestResult> |
Uploads with doUploadRequest when one is set, else with Stream. |
doDefaultUploadRequest(file, options?) |
Promise<MinimumUploadRequestResult> |
Uploads with Stream, as an image or a file. |
getUploadConfigCheck(file) |
Promise<UploadPermissionCheckResult> |
Checks a file against the app's upload settings: allowed and blocked types, and the size limit. |
initState({ message }?) |
void |
Loads a message's attachments, or empties the list. |
getSnapshot(), restoreSnapshot(snapshot) |
AttachmentManagerSnapshot, void |
Read and put back the list. Restoring cancels the uploads of the current list. |
AttachmentManager.toLocalUploadAttachment(file) is a static method that turns a File, Blob or FileReference into a pending local attachment with an id and a preview URL. AttachmentManager.fileToLocalUploadAttachment() and AttachmentManager.uploadAttachment() are deprecated: use AttachmentManager.toLocalUploadAttachment() and AttachmentManager.uploadFile().
Custom uploads
Uploads to your own storage are set up with attachments.doUploadRequest and attachments.customCdn, as Message composer shows. To choose which files are taken, add a pre-upload middleware that calls discard() for the ones to leave out.
The pre-upload chain also checks each file against the app's upload settings in Stream, even with customCdn on. Storage of your own may accept files Stream wouldn't. To skip that check, remove its middleware in the composer setup function. A key has one setup function, so put this in the same one as the rest of your composer setup:
client.config.setSetupFunction("messageComposer", ({ composer }) => {
composer.attachmentManager.preUploadMiddlewareExecutor.remove(
"stream-io/attachment-manager-middleware/file-upload-config-check",
);
});Attachment identity functions
stream-chat exports type guards for attachments. An attachment is local when its localMetadata has an id.
import { isLocalImageAttachment } from "stream-chat";
const firstImage = attachmentManager.attachments.find(isLocalImageAttachment);
console.log(firstImage?.original_width);| Function | Checks that the attachment is |
|---|---|
isLocalAttachment |
local |
isLocalUploadAttachment |
local, with upload metadata |
isPendingUpload |
local, with uploadState 'pending' or 'uploading' |
isFinishedUpload |
local, with uploadState: 'finished' |
isFailedUpload |
local, with uploadState: 'failed' |
isFileAttachment |
a file |
isLocalFileAttachment |
a local file |
isImageAttachment |
an image |
isLocalImageAttachment |
a local image |
isAudioAttachment |
audio |
isLocalAudioAttachment |
local audio |
isVoiceRecordingAttachment |
a voice recording |
isLocalVoiceRecordingAttachment |
a local voice recording |
isVideoAttachment |
a video |
isLocalVideoAttachment |
a local video |
isUploadedAttachment |
an uploaded file: audio, file, image, video or voice recording |
isScrapedContent |
a link preview: it has og_scrape_url or title_link |
isGiphyAttachment |
a Giphy |
isSharedLocationResponseData |
a shared location from a message |
Link preview management
composer.linkPreviewsManager is a LinkPreviewsManager. Its state has one field, previews, a Map from URL to LinkPreview. A LinkPreview is the preview's Open Graph data with a status, a LinkPreviewStatus:
| Status | Value | Meaning |
|---|---|---|
PENDING |
'pending' |
Not loaded yet. |
LOADING |
'loading' |
The preview is being fetched. |
LOADED |
'loaded' |
Fetched. Loaded previews are sent as attachments of the message. |
FAILED |
'failed' |
The fetch failed. |
DISMISSED |
'dismissed' |
The user removed it. It isn't fetched again while the URL stays in the text. |
Getters
previews reads the map. loadingPreviews, loadedPreviews, failedPreviews, dismissedPreviews and pendingPreviews return the previews in each status, as arrays. config is composer.config.linkPreviews.
enabled, debounceURLEnrichmentMs, findURLFn and onLinkPreviewDismissed are getters and setters for the settings of those names, on this composer only.
Methods
| Method | What it does |
|---|---|
findAndEnrichUrls(text) |
Finds the URLs in the text and fetches their previews, after debounceURLEnrichmentMs. The composer calls it when the text changes. |
cancelURLEnrichment() |
Cancels a scheduled fetch. |
updatePreview(url, data) |
Merges data into a preview, or adds one. |
dismissPreview(url) |
Marks a preview dismissed and calls onLinkPreviewDismissed. |
clearPreviews() |
Removes every preview except the dismissed ones. The composer calls it when the text is emptied. |
initState({ message }?) |
Loads a message's previews, or empties the map. |
getSnapshot(), restoreSnapshot(snapshot) |
Read and put back the state. |
The static methods LinkPreviewsManager.previewIsLoading(), LinkPreviewsManager.previewIsLoaded(), LinkPreviewsManager.previewIsFailed(), LinkPreviewsManager.previewIsDismissed() and LinkPreviewsManager.previewIsPending() check a preview's status. LinkPreviewsManager.getPreviewData(preview) returns the preview without its status, as it's sent.
Location composition
composer.locationComposer is a LocationComposer. Its state has one field, location: the location to share, or null. It holds latitude, longitude, created_by_device_id and message_id, and either durationMs for a live location being composed or end_at for one loaded from a message.
| Member | What it is |
|---|---|
location |
Getter: the location field. |
validLocation |
Getter: the location as it's sent, or null when it's incomplete or its durationMs is under location.minShareDurationMs. A durationMs becomes an end_at. |
deviceId |
Getter: the device id, from location.getDeviceId when the composer was built. |
config |
Getter: composer.config.location. |
setData({ latitude, longitude, durationMs? }) |
Sets the location, with this composer's id and the device id. Does nothing while location.enabled is off. With durationMs, the location is live for that long. |
initState({ message }?) |
Loads a message's location, or clears it. |
getSnapshot(), restoreSnapshot(snapshot) |
Read and put back the state. |
A latitude or longitude of exactly 0 counts as missing: LocationComposer.setData() ignores it, and validLocation is null.
To send a location on its own, call MessageComposer.sendLocation(). A location set in locationComposer also goes out with the next MessageComposer.send().
Poll composition
composer.pollComposer is a PollComposer. Its state holds the poll form and its errors:
| Field | Type | What it holds |
|---|---|---|
data.id |
string |
The poll's id. |
data.name |
string |
The question. |
data.description |
string |
A description. |
data.options |
{ id, text }[] |
The options. Starts with one empty option. |
data.max_votes_allowed |
string |
Votes per user, as typed. Empty means no limit. |
data.enforce_unique_vote |
boolean |
One vote per user. true by default. |
data.allow_answers |
boolean |
Users can add free-text answers. |
data.allow_user_suggested_options |
boolean |
Users can add options. |
data.voting_visibility |
VotingVisibility |
'public' by default. |
errors |
PollComposerFieldErrors |
Errors by field, and option errors by option id. Each has a stable code, such as validation:poll:name:required, and an English message. |
| Member | What it does |
|---|---|
updateFields(data, injectedFieldErrors?) |
Changes fields and validates them through the poll state chain. options takes the whole list, or { index, text } for one option. Errors you pass win over the computed ones. |
handleFieldBlur(field) |
Validates one field, as when the user leaves it. |
compose() |
Runs the poll composition chain and returns { data, errors } in the form the server takes, or undefined when the poll can't be created. |
canCreatePoll |
Getter: whether there's a question, at least one non-empty option, a valid max_votes_allowed and no errors. |
initState() |
Resets the form. |
getSnapshot(), restoreSnapshot(snapshot) |
Read and put back the state. |
The getters id, name, description, options, max_votes_allowed, enforce_unique_vote, allow_answers, allow_user_suggested_options and voting_visibility read the fields of data. stateMiddlewareExecutor and compositionMiddlewareExecutor are the poll's chains, described on Composer middleware.
To create the poll, call MessageComposer.createPoll(), then MessageComposer.send() to send the message that carries it.
Custom data management
composer.customDataManager is a CustomDataManager. Its state holds two objects:
| Field | Type | What it holds |
|---|---|---|
message |
CustomMessageData |
Fields added to the top level of the outgoing message. |
custom |
CustomMessageComposerData |
Data kept in the composer and never sent, such as a flag your UI reads. |
Type both by augmenting the CustomMessageData and CustomMessageComposerData interfaces of stream-chat, as Data conventions explains.
| Member | What it does |
|---|---|
customMessageData, customComposerData |
Getters: the message and custom fields. |
setMessageData(data) |
Merges fields into message. |
setCustomData(data) |
Merges fields into custom. |
isMessageDataEqual(next, previous?) |
Decides whether message changed, which counts as a change of the content, for drafts too. Compares the JSON of both by default. |
initState({ message }?) |
Empties both objects. They aren't restored from a draft or an edited message. |
getSnapshot(), restoreSnapshot(snapshot) |
Read and put back the state. |
CustomDataManager.setMessageData() writes to the top level of the message, not to message.custom. To send message.custom, add a composition middleware, as Composer middleware shows.
isMessageDataEqual is a property, so a setup function can replace it on each composer. This one compares the top-level values one by one, so the order of the keys doesn't matter, as it does for the default JSON comparison:
client.config.setSetupFunction("messageComposer", ({ composer }) => {
composer.customDataManager.isMessageDataEqual = (next, previous) => {
const nextData: Record<string, unknown> = { ...next.message };
const previousData: Record<string, unknown> = { ...previous?.message };
const keys = new Set([
...Object.keys(nextData),
...Object.keys(previousData),
]);
return [...keys].every((key) => nextData[key] === previousData[key]);
};
});Additional low-level exports
stream-chat exports more of the composer than the classes above. These are useful for custom uploads, suggestions and middleware:
| Group | Notable exports | Use them when |
|---|---|---|
| Configuration | MessageComposerConfig, AttachmentManagerConfig, TextComposerConfig, LinkPreviewsManagerConfig, LocationComposerConfig, PollComposerConfig, UploadRequestFn, MinimumUploadRequestResult, DEFAULT_COMPOSER_CONFIG and the per-part defaults, defaultCommandSendabilityValidator |
You type a custom upload request or command validator, or read the SDK's defaults. |
| Middleware | TextComposerMiddlewareExecutor, MessageComposerMiddlewareExecutor, MessageDraftComposerMiddlewareExecutor, AttachmentPreUploadMiddlewareExecutor, AttachmentPostUploadMiddlewareExecutor, PollComposerCompositionMiddlewareExecutor, PollComposerStateMiddlewareExecutor, and the create*Middleware factories |
You replace or extend one step of a chain. Composer middleware lists the built-in ones. |
| Search sources | MentionsSearchSource, CommandSearchSource |
You change how mentions or commands are searched. |
| Attachments and files | The identity functions above, isFileReference, isFileList, isImageFile, createFileFromBlobs, readFileAsArrayBuffer, generateFileName, getAttachmentTypeFromMimeType, ensureIsLocalAttachment, getAttachmentPreviewUrl |
You build custom drag and drop, paste, upload or attachment previews. |
| Polls | POLL_COMPOSER_VALIDATION_CODE, createPollComposerStateMiddleware, pollStateChangeValidators, MAX_POLL_OPTIONS |
You localize poll errors or add validation of your own. |
| Types | LocalAttachment, LocalUploadAttachment, FileReference, UploadPermissionCheckResult, TextComposerSuggestion<T>, MentionSuggestion, MentionEntity, UserSuggestion, ChannelMentionSuggestion, HereMentionSuggestion, RoleMentionSuggestion, UserGroupMentionSuggestion, CustomTextComposerSuggestion |
You type custom attachments, mentions, suggestions or middleware state. |
CustomTextComposerSuggestion is an interface: augment it in stream-chat to type the fields of your own suggestion items, for example interface CustomTextComposerSuggestion { label?: string } inside declare module "stream-chat". Declare the fields optional, because every suggestion type in the app shares the interface.