Attachment picker
AttachmentPicker is the bottom sheet the composer's attach button opens. Channel and Thread render it after their children, so every channel and thread screen has one. A selection bar at its top has a button for each source, photos, the camera, files, polls and commands, and the content below it shows the selected source. Attachments and uploads explains how picked files are uploaded, and Message composer UI describes the picker in the composer.
Best practices
- Configure the picker with the props of
ChannelandThread. It has no props of its own, and the two screens don't share their settings. - Pass the native header's height as
topInset, so the sheet stops below the header. Keyboard and insets explains the insets. - Install the native modules for the sources you want. A source whose module is missing has no button. See Installation.
- Change the buttons by replacing
AttachmentPickerSelectionBar, and the content by replacingAttachmentPickerContent. Render the exported buttons and contents in your replacement to keep the ones you want. - Open and close the picker with
openAttachmentPicker()andcloseAttachmentPicker()fromuseMessageInputContext(). Opening that way also closes the keyboard, and does nothing when the picker isn't rendered.
Basic usage
This channel screen sits below a native header and shows four photos per row:
import { useHeaderHeight } from "@react-navigation/elements";
import type { Channel as StreamChannel } from "stream-chat";
import {
Channel,
MessageComposer,
MessageList,
} from "stream-chat-react-native";
export const ChannelScreen = ({ channel }: { channel: StreamChannel }) => {
const headerHeight = useHeaderHeight();
return (
<Channel
channel={channel}
keyboardVerticalOffset={headerHeight}
numberOfAttachmentPickerImageColumns={4}
topInset={headerHeight}
>
<MessageList />
<MessageComposer />
</Channel>
);
};What it renders
The sheet opens to attachmentPickerBottomSheetHeight and renders:
AttachmentPickerSelectionBar, the row of source buttons.AttachmentPickerContent, the selected source, below the bar. WithdisableAttachmentPicker, the sheet renders the bar only, and each button opens the system picker directly.
Opening the picker closes the keyboard and selects the photo grid. Opening the keyboard closes it, and so does the Android back button. While it's open, the composer moves up above it. When shouldRenderAttachmentPicker is false, Channel and Thread don't render it, and openAttachmentPicker() does nothing; the attach button still shows, so hide it or give it a handleAttachButtonPress.
The default selection bar renders these buttons, each exported:
| Button | Shows when | With the sheet | With disableAttachmentPicker |
|---|---|---|---|
MediaPickerButton |
hasImagePicker |
selects the photo grid, "images" |
opens the native image picker |
CameraPickerButton |
hasCameraPicker |
selects "camera-photo"; on Android, a second button selects "camera-video" |
opens the camera |
FilePickerButton |
hasFilePicker |
selects "files" and opens the system file picker |
opens the system file picker |
PollPickerButton |
outside a thread, hasCreatePoll and the send-poll capability |
selects "polls" and opens the poll creation dialog |
opens the poll creation dialog |
CommandsPickerButton |
hasCommands |
selects "commands" |
opens the commands in a bottom sheet of its own |
AttachmentPickerContent renders, by the selected source:
| Source | Content | What it shows |
|---|---|---|
"images" |
AttachmentMediaPicker |
A grid of the device's photos and videos, loaded numberOfAttachmentImagesToLoadPerCall at a time. Tapping one uploads it into the composer, and tapping it again removes it. |
"camera-photo", "camera-video" |
AttachmentCameraPicker |
Opens the camera when it mounts, and a button that opens it again. When access was denied, a button that opens the settings. |
"files" |
AttachmentFilePicker |
A button that opens the system file picker. |
"polls" |
AttachmentPollPicker |
A button that opens the poll creation dialog. |
"commands" |
AttachmentCommandPicker |
The channel's commands. Tapping one makes it the active command and focuses the input. |
When the user limited the app to some photos on iOS, the grid starts with a tile that lets them pick more. A source name of your own renders nothing in the default content; see Add a source of your own.
UI customization
| Override key | Replaces | Props |
|---|---|---|
AttachmentPickerSelectionBar |
The row of source buttons | none |
AttachmentPickerContent |
The content below the bar | height |
ImageOverlaySelectedComponent |
The mark on each cell of the photo grid: a ring, or the cell's number among the attachments | index |
WithComponents also accepts AttachmentPickerIOSSelectMorePhotos, but no component reads it.
Props on Channel and Thread
| Prop | Type | Default | Description |
|---|---|---|---|
topInset |
number |
0 |
The space above the chat that isn't part of its views, such as a native header. The sheet doesn't open above it. |
bottomInset |
number |
0 |
The space the sheet keeps free at the bottom. While the picker is open, the composer moves up by the sheet's height minus it. |
attachmentPickerBottomSheetHeight |
number |
333, or 72 with disableAttachmentPicker |
The height of the open sheet. |
disableAttachmentPicker |
boolean |
true when no media library module is installed |
Renders the selection bar only. Its buttons open the system pickers directly. |
shouldRenderAttachmentPicker |
boolean |
true |
Whether the picker renders at all. |
numberOfAttachmentPickerImageColumns |
number |
3 |
The columns of the photo grid. |
numberOfAttachmentImagesToLoadPerCall |
number |
25 |
How many photos the grid loads at a time. |
hasImagePicker |
boolean |
whether an image picker or a media library is installed | Shows the photos button. |
hasCameraPicker |
boolean |
whether an image picker is installed | Shows the camera button. |
hasFilePicker |
boolean |
whether a document picker is installed | Shows the files button. |
hasCommands |
boolean |
whether the channel type has commands | Shows the commands button. |
hasCreatePoll |
boolean |
whether polls are on for the channel | Shows the poll button. true can't turn polls on where the composer's polls.enabled is false. |
openPollCreationDialog |
({ sendMessage }: Pick<LocalMessageInputContext, 'sendMessage'>) => void |
opens the default dialog | Called by the poll button in place of the default dialog. See Poll components. |
handleAttachButtonPress |
() => void |
opens or closes the picker | Replaces what the attach button does. |
focusInputOnPickerClose |
boolean |
true |
Whether pressing the attach button while the picker is open focuses the input, rather than only closing the picker. |
compressImageQuality |
number |
none | The quality, from 0 to 1, images are compressed to before upload. |
attachmentSelectionBarHeight |
number |
72 |
Not read by any component. |
The full list of Channel props is on Channel.
Component props
AttachmentPicker, AttachmentPickerSelectionBar, the five buttons and AttachmentCommandPicker take no props. They read the picker's context and MessageInputContext.
AttachmentPickerContent, AttachmentMediaPicker, AttachmentFilePicker and AttachmentPollPicker take AttachmentPickerContentProps:
| Prop | Type | Default | Description |
|---|---|---|---|
height |
number |
none | The height of the content. AttachmentPicker passes the sheet's height minus the bar's. |
AttachmentCameraPicker also takes videoOnly?: boolean, which opens the camera for a video. AttachmentCommandPickerItem takes item: CommandSuggestion, the command it shows.
ImageOverlaySelectedComponent:
| Prop | Type | Default | Description |
|---|---|---|---|
index |
number |
required | The cell's position among the composer's attachments, or -1 when it isn't attached. The default shows a ring at -1, and index + 1 otherwise. |
AttachmentTypePickerButton is the button of the selection bar, for a source of your own:
| Prop | Type | Default | Description |
|---|---|---|---|
Icon |
ButtonProps['LeadingIcon'] |
required | The icon, rendered at 20 points. |
onPress |
null | ((event: PressableEvent) => void) |
none | Called on press, unless the button is selected and the sheet is in use. |
selected |
boolean |
none | Highlights the button, unless disableAttachmentPicker is true. |
accessibilityLabelKey |
string |
none | The translation key of the button's accessibility label. |
testID |
string |
none | The test ID of the button. |
AttachmentPickerGenericContent is the content the camera, files and polls sources show, for a source of your own:
| Prop | Type | Default | Description |
|---|---|---|---|
Icon |
React.ComponentType<IconProps> |
required | The icon above the text. |
onPress |
() => void |
required | Called by the button. |
buttonText |
string |
none | The button's label. |
description |
string |
none | The text above the button. |
height |
number |
none | The height of the content. |
AttachmentPickerContext
Channel and Thread provide AttachmentPickerContext, which useAttachmentPickerContext() reads. It throws outside both.
| Value | Type | Description |
|---|---|---|
attachmentPickerStore |
AttachmentPickerStore |
The store of the selected source. setSelectedPicker(name) selects one, and undefined clears it. useAttachmentPickerState() reads it. |
openPicker |
() => void |
Opens the sheet. The sheet selects the photo grid when it has opened. |
closePicker |
() => void |
Closes the sheet. The sheet clears the selected source when it has closed. |
bottomSheetRef |
React.RefObject<BottomSheet | null> |
The ref of the sheet, a @gorhom/bottom-sheet BottomSheet. |
topInset |
number |
The Channel or Thread prop, 0 by default. |
bottomInset |
number |
The Channel or Thread prop, 0 by default. |
attachmentPickerBottomSheetHeight |
number |
The Channel or Thread prop. |
attachmentSelectionBarHeight |
number |
The Channel or Thread prop. No component reads it. |
disableAttachmentPicker |
boolean |
The Channel or Thread prop. |
shouldRenderAttachmentPicker |
boolean |
The Channel or Thread prop. |
numberOfAttachmentPickerImageColumns |
number |
The Channel or Thread prop. |
numberOfAttachmentImagesToLoadPerCall |
number |
The Channel or Thread prop. |
AttachmentPickerProvider and AttachmentPickerContext are exported too. AttachmentPickerProvider takes openPicker, closePicker, topInset and bottomInset and creates the store; Channel and Thread render it for you.
Examples
Add a source of your own
A source is a name in attachmentPickerStore. This selection bar adds a button that selects "gifs", and the content renders a component of your own for it and the SDK's content for the other sources:
import { Text, View } from "react-native";
import type { ComponentOverrides } from "stream-chat-react-native";
import type { AttachmentPickerContentProps } from "stream-chat-react-native";
import {
AttachmentPickerContent,
AttachmentTypePickerButton,
CameraPickerButton,
FilePickerButton,
MediaPickerButton,
useAttachmentPickerContext,
useAttachmentPickerState,
useComponentsContext,
} from "stream-chat-react-native";
const GifPickerButton = () => {
const { attachmentPickerStore } = useAttachmentPickerContext();
const { selectedPicker } = useAttachmentPickerState();
const { icons } = useComponentsContext();
return (
<AttachmentTypePickerButton
Icon={icons.GiphyIcon}
onPress={() => attachmentPickerStore.setSelectedPicker("gifs")}
selected={selectedPicker === "gifs"}
/>
);
};
const SelectionBarWithGifs = () => (
<View style={{ flexDirection: "row", paddingHorizontal: 12 }}>
<MediaPickerButton />
<CameraPickerButton />
<FilePickerButton />
<GifPickerButton />
</View>
);
const ContentWithGifs = (props: AttachmentPickerContentProps) => {
const { selectedPicker } = useAttachmentPickerState();
if (selectedPicker === "gifs") {
return (
<View style={{ height: props.height, padding: 16 }}>
<Text>Your GIF search goes here.</Text>
</View>
);
}
return <AttachmentPickerContent {...props} />;
};
// Pass to `WithComponents`, declared once at module scope.
export const pickerOverrides: ComponentOverrides = {
AttachmentPickerContent: ContentWithGifs,
AttachmentPickerSelectionBar: SelectionBarWithGifs,
};A button of your own isn't hidden by disableAttachmentPicker, which leaves the content out. Add files from your content with uploadNewFile(file) from useMessageInputContext(). The Location sharing UI cookbook adds a location source this way.
Hooks
| Hook | Returns |
|---|---|
useAttachmentPickerContext() |
the values above; throws outside Channel and Thread |
useAttachmentPickerState() |
{ selectedPicker }, the selected source, or undefined while the picker is closed |
useMessageInputContext() |
openAttachmentPicker(), closeAttachmentPicker(), the pickers' functions and the has*Picker settings |
See useAttachmentPickerContext and useAttachmentPickerState.