Attachments
Attachment renders what a message carries: images, videos, files, audio, voice recordings, Giphy, link previews and a shared location. It sorts the attachments into groups and renders each group with the component its prop names. Attachments and uploads explains how attachments get there and how they show.
Best practices
- Replace one renderer by passing it to
Attachment, and register the wrapper as theAttachmentoverride. The defaultMessageUIis the only part that reads that key. - Pass components and handlers that don't change, declared outside the component.
Attachmentbuilds its groups again only whenattachmentsorattachmentActionsDefaultFocuschanges. - In a custom message UI, pass
message.shared_locationtogether withmessage.attachments. The location isn't inattachments.
Default rendering
Attachment renders the groups in this order:
| Group | Attachments | Rendered by |
|---|---|---|
| Media | images, type: 'video', and anything with an MP4, Ogg, WebM or QuickTime custom.mime_type |
Image for one image, Media for one video, ModalGallery for several |
| Giphy | type: 'giphy' |
Giphy, with AttachmentActions while it's a preview with buttons |
| Card | anything with og_scrape_url or title_link, such as a link preview |
Card, with AttachmentActions when it has actions |
| Geolocation | the shared location | Geolocation |
| File | audio, voice recordings, type: 'file', and anything else with a custom.mime_type |
Audio, VoiceRecording and File |
| Unsupported | anything else | UnsupportedAttachment |
A Giphy and a link preview are checked first: an image with og_scrape_url is a card. A file, an audio file or a voice recording with neither a URL nor a local upload renders nothing.
Unsupported attachments
An attachment that fits no other group goes to UnsupportedAttachment: an empty object, or one with no custom.mime_type and a type the SDK doesn't know, such as a type of your own. The default shows an icon and "Unsupported attachment". It takes no props.
Attachment renders the component on this prop with { attachment }, typed UnsupportedAttachmentProps, so a replacement can render types of its own and fall back to the default for the rest. Change how attachments render has an example.
Basic usage
The default MessageUI renders Attachment for you. A custom message UI renders it with the message's attachments and location, and passes handleAction for the buttons of a Giphy preview:
import { Attachment, MessageText, useMessageContext } from "stream-chat-react";
export const MessageWithAttachments = () => {
const { handleAction, message } = useMessageContext();
const attachments = [
...(message.shared_location ? [message.shared_location] : []),
...(message.attachments ?? []),
];
return (
<div className="message-with-attachments">
{attachments.length > 0 && (
<Attachment actionHandler={handleAction} attachments={attachments} />
)}
<MessageText />
</div>
);
};UI customization
This wrapper renders files with a component of its own and keeps the other renderers:
import type { AttachmentProps, FileAttachmentProps } from "stream-chat-react";
import { Attachment, WithComponents } from "stream-chat-react";
const FileLink = ({ attachment }: FileAttachmentProps) => (
<a download href={attachment.asset_url} rel="noreferrer" target="_blank">
{attachment.title ?? "Download file"}
</a>
);
const AttachmentWithFileLinks = (props: AttachmentProps) => (
<Attachment {...props} File={FileLink} />
);
const overrides = { Attachment: AttachmentWithFileLinks };
export const App = () => (
<WithComponents overrides={overrides}>
{/* the rest of the app */}
</WithComponents>
);The default Image renders the ModalGallery override key with one item, not the ModalGallery prop. To change both single images and galleries, set the key. The BaseImage, ImagePlaceholder, VideoPlayer, AttachmentFileIcon and AttachmentUploadProgressIndicator keys change parts inside the renderers; see BaseImage and ComponentContext.
Image and video sizing
The CSS variable --str-chat__attachment-max-width sets the largest width and height of images and videos. It must resolve to pixels in getComputedStyle, for example 300px, 10rem or calc(300px - var(--margin)). When it doesn't, the SDK can't size attachments, logs a warning, and the message list can scroll to the wrong place.
The default size handlers ask Stream's CDN for a copy that fits:
- They read the original size from the
ohandowparameters of the attachment's URL. - They read the element's
max-width, and itsmax-heightorheight, in pixels. - They add
handwparameters for twice that size, for high-density screens.
For a video, the same happens to thumb_url, and only while shouldGenerateVideoThumbnail is on. The video itself loads from asset_url.
When your files aren't on Stream's CDN, give Attachment handlers of your own. This one asks an image service for a copy twice the element's maximum width:
import type {
AttachmentProps,
ImageAttachmentSizeHandler,
} from "stream-chat-react";
import { Attachment, WithComponents } from "stream-chat-react";
const imageFromMyCdn: ImageAttachmentSizeHandler = (attachment, element) => {
const source = attachment.image_url ?? attachment.thumb_url ?? "";
const maxWidth = parseFloat(getComputedStyle(element).maxWidth) || 300;
const width = Math.round(maxWidth * 2);
return {
url: `https://images.example.com/resize?width=${width}&src=${encodeURIComponent(source)}`,
};
};
const AttachmentFromMyCdn = (props: AttachmentProps) => (
<Attachment {...props} imageAttachmentSizeHandler={imageFromMyCdn} />
);
const overrides = { Attachment: AttachmentFromMyCdn };
export const App = () => (
<WithComponents overrides={overrides}>
{/* the rest of the app */}
</WithComponents>
);For an image or a Giphy, an empty url keeps the attachment's own URL. A video handler has to return the video's url, as the default does with asset_url. The components inside Attachment read these settings from AttachmentContext.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
attachments |
(Attachment | SharedLocationResponseData)[] |
required | The attachments to render, the shared location included. |
actionHandler |
(dataOrName?: string | FormData, value?: string, event?: BaseSyntheticEvent) => Promise<void> | void |
Called by the attachments' action buttons, such as a Giphy preview's send, shuffle and cancel. Pass handleAction from the context. |
|
AttachmentActions |
ComponentType<AttachmentActionsProps> |
AttachmentActions |
Renders an attachment's action buttons. |
attachmentActionsDefaultFocus |
AttachmentActionsDefaultFocusByType |
{ giphy: 'send' } |
Which action button gets the focus, by attachment type. Pass {} to focus none. |
Audio |
ComponentType<AudioProps> |
Audio |
Renders an audio file. |
Card |
ComponentType<CardProps> |
Card |
Renders a link preview. Receives the attachment's fields as props. |
File |
ComponentType<FileAttachmentProps> |
FileAttachment |
Renders a file. |
Geolocation |
ComponentType<GeolocationProps> |
Geolocation |
Renders the shared location. |
Giphy |
ComponentType<{ attachment: Attachment }> |
the SDK's Giphy component | Renders a Giphy. The default isn't exported. |
giphyVersion |
GiphyVersions |
'fixed_height' |
The Giphy rendition to show, in Giphy attachments and cards. |
Image |
ComponentType<ImageProps> |
ImageComponent |
Renders a single image. Receives the image as a gallery item. |
imageAttachmentSizeHandler |
(attachment: Attachment, element: HTMLElement) => { url: string } |
the CDN handler above | Picks the URL to load for an image and for a Giphy, from the rendered element. |
isQuoted |
boolean |
false |
Renders voice recordings in the compact form of a quoted message. |
Media |
ComponentType<{ controls?: boolean; isPlaying?: boolean; thumbnailUrl?: string; videoUrl?: string }> |
the SDK's video player | Plays a single video. The default uses the VideoPlayer key when set, else loads react-player on demand. |
ModalGallery |
ComponentType<ModalGalleryProps> |
ModalGallery |
Renders several images and videos as thumbnails that open a full-screen gallery. |
shouldGenerateVideoThumbnail |
boolean |
true |
Shows a video's thumb_url with a play button until the user plays it. With false, the player shows at once. |
UnsupportedAttachment |
ComponentType<UnsupportedAttachmentProps> |
UnsupportedAttachment |
Renders an attachment no other group takes. Receives { attachment }. |
videoAttachmentSizeHandler |
(attachment: Attachment, element: HTMLElement, shouldGenerateVideoThumbnail: boolean) => { thumbUrl?: string; url: string } |
the CDN handler above | Picks the URLs to load for a video and its thumbnail. |
VoiceRecording |
ComponentType<VoiceRecordingProps> |
VoiceRecording |
Renders a voice recording. See Voice Recording Attachment. |
The handler types are exported as ImageAttachmentSizeHandler and VideoAttachmentSizeHandler.
Hooks
useAttachmentContext()returnsgiphyVersion, the two size handlers andshouldGenerateVideoThumbnail, for a renderer of your own. See AttachmentContext.useAttachmentUploadState(attachment)returnsisUploading,progressanduploadConfirmationPending, for a renderer that shows an upload in progress.useMessageContext()giveshandleActionand the message.