# Image gallery

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

The image gallery is the full-screen viewer for the images, videos and GIFs of messages. `OverlayProvider` renders it, above your navigation, while its overlay is `'gallery'`, and reads what to show from an `ImageGalleryStateStore` in `ImageGalleryContext`. This page covers how it opens, its parts and their override keys, the context and the store. [Overlays and bottom sheets](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/overlays-and-bottom-sheets/#the-image-gallery) shows it from the user's side.

## Best practices

- Open the gallery through the store and the overlay, as the SDK does: `imageGalleryStateStore.openImageGallery()`, then `setOverlay("gallery")`. Don't render `ImageGallery` yourself; `OverlayProvider` already does.
- Put the `WithComponents` that replaces a gallery part around `OverlayProvider`. The gallery renders there, outside your screens.
- Pass `requesterNode`, the native handle of the thumbnail that opened the gallery. Screen reader focus returns to it when the gallery closes.
- A header of your own takes over clearing the store. The default header calls `imageGalleryStateStore.clear()` when the gallery closes, which also stops its videos; call it when yours unmounts.
- Set `autoPlayVideo`, `giphyVersion` and `numberOfImageGalleryGridColumns` on `OverlayProvider`. The store reads `giphyVersion` once, when `OverlayProvider` mounts.

## How it opens and closes

A component opens the gallery in two steps:

1. `imageGalleryStateStore.openImageGallery({ messages, selectedAttachmentUrl, requesterNode })` gives the store the messages to show and the attachment to start on.
2. `setOverlay("gallery")` from `useOverlayContext()` makes `OverlayProvider` render `ImageGallery`.

The store keeps the messages that have an attachment it can show: an image that isn't a link preview, a Giphy attachment, or a video when a video player is installed. It turns every attachment of those messages into an asset, so a file sent in the same message as an image becomes a slide too. It starts on the asset whose URL matches `selectedAttachmentUrl`, ignoring the query string, or on the first one.

A message's gallery attachment opens it with that one message, so the user swipes through the message's images and videos. The media list of channel details opens it with every message it has loaded. Inside channel details, the list is in a native modal that covers `OverlayProvider`'s layer, so the navigation section renders a gallery inside the modal too.

The back button of the header, a swipe down, and the Android back button close the gallery by setting the overlay to `'none'`.

## Parts

| Part                                                           | Component                  | Override key                |
| -------------------------------------------------------------- | -------------------------- | --------------------------- |
| The viewer: the pager, zoom and the gestures                   | `ImageGallery`             | None                        |
| The header: the back button, the sender and the time           | `ImageGalleryHeader`       | `ImageGalleryHeader`        |
| The footer: the share button, the position and the grid button | `ImageGalleryFooter`       | `ImageGalleryFooter`        |
| The video controls, at the top of the footer for a video       | `ImageGalleryVideoControl` | `ImageGalleryVideoControls` |
| The grid of every asset, in a bottom sheet                     | `ImageGalleryGrid`         | `ImageGalleryGrid`          |

The header shows the sender's name, or their id, or "Unknown User", and the time the message was sent, formatted with the `timestamp.ImageGalleryHeader` translation key. The footer shows the position, such as "2 of 5". Its share button saves a remote asset to a file and opens the share sheet, and shows only when the share and file system handlers are registered. The video controls have play and pause, the position, a seek bar and a `SpeedSettingsButton`. Pressing an asset in the grid moves the gallery to it and closes the grid.

## ImageGallery props

`ImageGallery` reads all of its props from context, and you don't render it in an app. They're listed for a custom overlay that renders it.

| Prop                              | Type                                           | Default                      | Description                                                      |
| --------------------------------- | ---------------------------------------------- | ---------------------------- | ---------------------------------------------------------------- |
| `numberOfImageGalleryGridColumns` | `number`                                       | from `ImageGalleryContext`   | The columns of the grid. Without a value in context, `3`.        |
| `overlayOpacity`                  | `SharedValue<number>`                          | from `OverlayContext`        | The opacity the gestures fade while the gallery is dragged down. |
| `ImageGalleryHeader`              | `React.ComponentType<ImageGalleryHeaderProps>` | the `ImageGalleryHeader` key | The header.                                                      |
| `ImageGalleryFooter`              | `React.ComponentType<ImageGalleryFooterProps>` | the `ImageGalleryFooter` key | The footer.                                                      |
| `ImageGalleryGrid`                | `React.ComponentType<ImageGalleryGridProps>`   | the `ImageGalleryGrid` key   | The grid.                                                        |

## Part props

The gallery passes these props to the parts. A replacement reads the asset it shows from the store, as the defaults do.

`ImageGalleryHeaderProps`:

| Prop      | Type                  | Default  | Description                                                                         |
| --------- | --------------------- | -------- | ----------------------------------------------------------------------------------- |
| `opacity` | `SharedValue<number>` | required | Fades from `1` to `0` while the user drags the image down.                          |
| `visible` | `SharedValue<number>` | required | `1` while the header and footer show, `0` after a tap hides them, animated between. |

`ImageGalleryFooterProps`:

| Prop                 | Type                  | Default  | Description                                                        |
| -------------------- | --------------------- | -------- | ------------------------------------------------------------------ |
| `accessibilityLabel` | `string`              | required | The label of the footer. The gallery passes a fixed English label. |
| `opacity`            | `SharedValue<number>` | required | As for the header.                                                 |
| `openGridView`       | `() => void`          | required | Opens the grid.                                                    |
| `visible`            | `SharedValue<number>` | required | As for the header.                                                 |

The default footer renders the `ImageGalleryVideoControls` key when the current asset is a video.

`ImageGalleryGridProps`:

| Prop                              | Type         | Default  | Description                                         |
| --------------------------------- | ------------ | -------- | --------------------------------------------------- |
| `closeGridView`                   | `() => void` | required | Closes the grid.                                    |
| `numberOfImageGalleryGridColumns` | `number`     |          | The columns. The default grid uses `3` without one. |

`ImageGalleryVideoControlProps`:

| Prop           | Type     | Default  | Description                                                            |
| -------------- | -------- | -------- | ---------------------------------------------------------------------- |
| `attachmentId` | `string` | required | The id of the current asset, which is also the id of its video player. |

## ImageGalleryContext

`OverlayProvider` provides it, through `ImageGalleryProvider`, from its own props of the same names. `useImageGalleryContext()` reads it, and throws outside `OverlayProvider`.

| Value                             | Type                                     | Description                                                                             |
| --------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------- |
| `imageGalleryStateStore`          | `ImageGalleryStateStore`                 | The store of the gallery. The provider creates it once and registers its subscriptions. |
| `autoPlayVideo`                   | `boolean`                                | Whether a video starts playing when the gallery shows it. `false` when unset.           |
| `giphyVersion`                    | `keyof NonNullable<Attachment['giphy']>` | The Giphy rendition the gallery shows. The store uses `'fixed_height'` when unset.      |
| `numberOfImageGalleryGridColumns` | `number`                                 | The columns of the grid.                                                                |

`ImageGalleryProvider` takes them as `value`, of type `ImageGalleryProviderProps`, without the store.

## ImageGalleryStateStore

The store holds what the gallery shows. Its `state` is a `StateStore` of `ImageGalleryState`; read it with `useStateStore` and a selector at module scope.

| Field                   | Type                  | Description                                                                                                                    |
| ----------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `messages`              | `LocalMessage[]`      | The messages the gallery was opened with.                                                                                      |
| `assets`                | `ImageGalleryAsset[]` | One asset per attachment of the messages with an attachment it can show, in message order. Recomputed when `messages` changes. |
| `currentIndex`          | `number`              | The index of the asset on screen.                                                                                              |
| `selectedAttachmentUrl` | `string`              | The URL to start on, when set. Setting it moves `currentIndex` to its asset.                                                   |
| `requesterNode`         | `number \| null`      | The native handle that gets screen reader focus back when the gallery closes.                                                  |

An `ImageGalleryAsset` has the asset's `id`, `uri`, `type`, `mime_type`, `thumb_url`, `original_width` and `original_height`, and its message's `messageId`, `channelId`, `created_at`, `user` and `user_id`.

| Member                                                                   | What it does                                                                              |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| `openImageGallery({ messages, selectedAttachmentUrl?, requesterNode? })` | Sets the messages, the URL to start on and the requester in one update.                   |
| `appendMessages(messages)`, `removeMessages(messages)`                   | Adds or removes messages while the gallery is open.                                       |
| `currentIndex`                                                           | A setter that moves the gallery to an asset, and pauses the video of the asset it leaves. |
| `clear()`                                                                | Empties the state and removes every video player.                                         |
| `videoPlayerPool`                                                        | The video players of the gallery, one per video asset, keyed by the asset's id.           |

`useImageGalleryVideoPlayer({ id })` returns the player of an asset from the pool, creating it when it's missing, with `autoPlayVideo` from the context. The default video controls read its `state` for `isPlaying`, `progress`, `duration` and `currentPlaybackRate`, and call `toggle()`, `seek()` and `changePlaybackRate()`.

## Theming

The theme's `imageGallery` key styles the gallery: `backgroundColor`, `pager` and `slide` for the viewer, and `header`, `footer`, `grid` and `videoControl` for the parts. See [Theme object](https://getstream.io/chat/docs/sdk/react-native/v10/reference/theme/theme-object/#imagegallery).

## Examples

### Open the gallery from your own thumbnails

```tsx title="src/MessageThumbnails.tsx"
import { useRef } from "react";
import type { ComponentRef } from "react";
import { findNodeHandle, Image, Pressable, View } from "react-native";
import type { LocalMessage } from "stream-chat";
import {
  useImageGalleryContext,
  useOverlayContext,
} from "stream-chat-react-native";

export const MessageThumbnails = ({ message }: { message: LocalMessage }) => {
  const { imageGalleryStateStore } = useImageGalleryContext();
  const { setOverlay } = useOverlayContext();
  const refs = useRef<Record<string, ComponentRef<typeof View> | null>>({});

  const images = (message.attachments ?? []).filter(
    (attachment) => attachment.type === "image" && attachment.image_url,
  );

  return (
    <View style={{ flexDirection: "row", gap: 4 }}>
      {images.map((attachment) => {
        const url = attachment.image_url as string;
        return (
          <Pressable
            accessibilityLabel="Open image"
            accessibilityRole="button"
            key={url}
            onPress={() => {
              imageGalleryStateStore.openImageGallery({
                messages: [message],
                requesterNode: findNodeHandle(refs.current[url]),
                selectedAttachmentUrl: url,
              });
              setOverlay("gallery");
            }}
            ref={(view) => {
              refs.current[url] = view;
            }}
          >
            <Image source={{ uri: url }} style={{ height: 64, width: 64 }} />
          </Pressable>
        );
      })}
    </View>
  );
};
```

### A header of your own

```tsx title="src/GalleryHeader.tsx"
import { useEffect } from "react";
import { Pressable, Text, View } from "react-native";
import Animated, { useAnimatedStyle } from "react-native-reanimated";
import {
  useImageGalleryContext,
  useOverlayContext,
  useStateStore,
} from "stream-chat-react-native";
import type {
  ComponentOverrides,
  ImageGalleryHeaderProps,
  ImageGalleryState,
} from "stream-chat-react-native";

const selector = ({ assets, currentIndex }: ImageGalleryState) => ({
  senderName: assets[currentIndex]?.user?.name,
});

const GalleryHeader = ({ opacity }: ImageGalleryHeaderProps) => {
  const { imageGalleryStateStore } = useImageGalleryContext();
  const { setOverlay } = useOverlayContext();
  const { senderName } = useStateStore(imageGalleryStateStore.state, selector);
  const style = useAnimatedStyle(() => ({ opacity: opacity.value }));

  // The default header clears the store when the gallery closes; this one has to as well.
  useEffect(
    () => () => imageGalleryStateStore.clear(),
    [imageGalleryStateStore],
  );

  return (
    <Animated.View style={[{ paddingTop: 48 }, style]}>
      <View
        style={{
          flexDirection: "row",
          justifyContent: "space-between",
          padding: 12,
        }}
      >
        <Pressable
          accessibilityRole="button"
          onPress={() => setOverlay("none")}
        >
          <Text>Close</Text>
        </Pressable>
        <Text>{senderName ?? ""}</Text>
      </View>
    </Animated.View>
  );
};

// Pass these to a WithComponents around OverlayProvider.
export const galleryOverrides: ComponentOverrides = {
  ImageGalleryHeader: GalleryHeader,
};
```

## Related

- [OverlayProvider](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/chat/overlayprovider/): the props that configure the gallery.
- [OverlayContext](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/chat/overlaycontext/): `overlay`, `setOverlay` and `overlayOpacity`.
- [Attachments and uploads](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/attachments-and-uploads/#change-what-a-tap-does): changing what a tap on a thumbnail does.
- [Channel details lists](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/channel-details/channel-details-lists/): `MediaList`, which opens the gallery on the channel's media.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/utilities/image-gallery/](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/utilities/image-gallery/).