From 660c0b8f81ce81c71de76116c61f55a1692800ed Mon Sep 17 00:00:00 2001 From: Stream SDK Bot <60655709+Stream-SDK-Bot@users.noreply.github.com> Date: Thu, 2 Jul 2026 09:18:23 +0100 Subject: [PATCH 1/5] chore: update sdk size (#3695) This PR was created automatically by CI. Co-authored-by: Stream Bot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe06e4b4b4..d466b59f5b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![NPM](https://img.shields.io/npm/v/stream-chat-react-native.svg)](https://www.npmjs.com/package/stream-chat-react-native) [![Build Status](https://github.com/GetStream/stream-chat-react-native/actions/workflows/release.yml/badge.svg)](https://github.com/GetStream/stream-chat-react-native/actions) [![Component Reference](https://img.shields.io/badge/docs-component%20reference-blue.svg)](https://getstream.io/chat/docs/sdk/reactnative) -![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-1972%20KB-blue) +![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-1974%20KB-blue) From 0e5a005fd7c7c082f6ef0c8a237812d6cf525bbf Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj <31964049+isekovanic@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:38:23 +0200 Subject: [PATCH 2/5] perf: prevent recycling remounting in MessageContent descendants (#3696) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal This PR fixes a relatively critical performance issue with our `MessageFlashList`, where due to how our component structure works basically any downstream leaf of `MessageContent` was being hard remounted. Passing non-recycling stable keys essentially kills the entire point of recycling and defeats our main mechanism to gain performance here. There are still some more (perhaps not so obvious places) where this can be improved, however we want to take this one step at a time and do it carefully since these changes are quite finicky. Another PR will follow with performance improvements to the way we fetch the item type (also responsible for some remounts). Later on we can also explore using the `useMappingHelper` hook from the library itself and seeing if that'll improve even further. But, one step at a time. ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes
iOS
Before After
Android
Before After
## ๐Ÿงช Testing ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- package/src/components/Attachment/FileAttachmentGroup.tsx | 4 ++-- package/src/components/Attachment/Gallery.tsx | 2 +- .../src/components/Message/MessageItemView/MessageContent.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package/src/components/Attachment/FileAttachmentGroup.tsx b/package/src/components/Attachment/FileAttachmentGroup.tsx index 84374798b3..d89cb934c3 100644 --- a/package/src/components/Attachment/FileAttachmentGroup.tsx +++ b/package/src/components/Attachment/FileAttachmentGroup.tsx @@ -17,7 +17,7 @@ export type FileAttachmentGroupPropsWithContext = Pick { - const { files, message, styles: stylesProp = {} } = props; + const { files, styles: stylesProp = {} } = props; const { Attachment } = useComponentsContext(); const { @@ -32,7 +32,7 @@ const FileAttachmentGroupWithContext = (props: FileAttachmentGroupPropsWithConte {files.map((file, index) => ( diff --git a/package/src/components/Attachment/Gallery.tsx b/package/src/components/Attachment/Gallery.tsx index 5a1b0bd103..b675a24725 100644 --- a/package/src/components/Attachment/Gallery.tsx +++ b/package/src/components/Attachment/Gallery.tsx @@ -272,7 +272,7 @@ const GalleryThumbnail = ({ accessibilityLabel={thumbnailAccessibilityLabel} accessibilityRole='button' disabled={preventPress} - key={`gallery-item-${message.id}/${colIndex}/${rowIndex}/${imagesAndVideos.length}`} + key={`gallery-item-${colIndex}/${rowIndex}`} ref={thumbnailRef} onLongPress={(event) => { if (onLongPress) { diff --git a/package/src/components/Message/MessageItemView/MessageContent.tsx b/package/src/components/Message/MessageItemView/MessageContent.tsx index 716dd4d651..8f699f06ee 100644 --- a/package/src/components/Message/MessageItemView/MessageContent.tsx +++ b/package/src/components/Message/MessageItemView/MessageContent.tsx @@ -281,7 +281,7 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { ); case 'attachments': return otherAttachments.map((attachment, attachmentIndex) => ( - + )); case 'files': return ( @@ -297,7 +297,7 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { const pollId = message.poll_id; const poll = pollId && client.polls.fromState(pollId); return pollId && poll ? ( - + ) : null; } case 'location': From bcbf406f28ddd20b5d3fa34be1c8898764eacd74 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj <31964049+isekovanic@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:42:45 +0200 Subject: [PATCH 3/5] perf: optimize recycling item type resolution (#3697) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal This PR is a followup to [this change](https://github.com/GetStream/stream-chat-react-native/pull/3696). In addition to making sure React doesn't remount components while recycling, we also have to make sure to try resolving components in the recycling pool as fast as possible. For instance, if all messages with attachments belong to the same pool we can get into a situation where we try to recycle something like a voice recording into a gallery. This, needless to say is going to be super suboptiomal. Possibly worse than having no item type at all (since voice recording as an example has so many subcomponents it would take some serious effort to clean them up from the GC side as well as all of the Fabric nodes). With this change we try to improve that by making the recycling pool (especially for attachments) way more explicit. ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes
iOS
Before After
Android
Before After
## ๐Ÿงช Testing ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- .../MessageList/MessageFlashList.tsx | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/package/src/components/MessageList/MessageFlashList.tsx b/package/src/components/MessageList/MessageFlashList.tsx index 7c6d75a341..46c99b714e 100644 --- a/package/src/components/MessageList/MessageFlashList.tsx +++ b/package/src/components/MessageList/MessageFlashList.tsx @@ -55,9 +55,11 @@ import { mergeThemes, useTheme } from '../../contexts/themeContext/ThemeContext' import { ThreadContextValue, useThreadContext } from '../../contexts/threadContext/ThreadContext'; import { useStableCallback, useStateStore } from '../../hooks'; +import { isVideoPlayerAvailable } from '../../native'; import { bumpOverlayLayoutRevision, useHasActiveId } from '../../state-store'; import { MessageInputHeightState } from '../../state-store/message-input-height-store'; import { primitives } from '../../theme'; +import { FileTypes } from '../../types/types'; import { transitions } from '../../utils/animations/transitions'; import { MessageWrapper } from '../Message/MessageItemView/MessageWrapper'; import { excludeCanceledUploadNotifications } from '../Notifications/notificationFilters'; @@ -222,10 +224,61 @@ type MessageFlashListPropsWithContext = Pick< const WAIT_FOR_SCROLL_TIMEOUT = 0; +// Classify an attachment bearing message by its primary shape so FlashList only +// recycles same shaped cells (means less work to rerender). Gallery/media is the +// heaviest subtree to mount, so we short circuit to it as soon as we see one gallery +// image/video nad this keeps gallery cells recycling only with other gallery cells, +// so the Gallery subtree reconciles on rebind instead of unmount & remount. Mirrors +// the attachment categorization in Message. +const getAttachmentItemType = (message: LocalMessage) => { + const attachments = message.attachments ?? []; + let hasGiphy = false; + let hasAudio = false; + let hasFile = false; + let hasCard = false; + for (const attachment of attachments) { + const isGalleryImage = + attachment.type === FileTypes.Image && + !attachment.og_scrape_url && + !attachment.title_link && + (!!attachment.image_url || !!attachment.thumb_url); + const isGalleryVideo = + attachment.type === FileTypes.Video && !attachment.og_scrape_url && isVideoPlayerAvailable(); + if (isGalleryImage || isGalleryVideo) { + return 'message-with-gallery'; + } + if (attachment.type === FileTypes.Giphy) { + hasGiphy = true; + } else if ( + attachment.type === FileTypes.Audio || + attachment.type === FileTypes.VoiceRecording + ) { + hasAudio = true; + } else if (attachment.type === FileTypes.File) { + hasFile = true; + } else if (attachment.og_scrape_url || attachment.title_link) { + hasCard = true; + } + } + if (hasGiphy) { + return 'message-with-giphy'; + } + if (hasAudio) { + return 'message-with-audio'; + } + if (hasFile) { + return 'message-with-file'; + } + if (hasCard) { + return 'message-with-card'; + } + return 'message-with-attachments'; +}; + const getItemTypeInternal = (message: LocalMessage) => { if (message.type === 'regular') { if ((message.attachments?.length ?? 0) > 0) { - return 'message-with-attachments'; + return getAttachmentItemType(message); } if (message.poll_id) { @@ -1079,7 +1132,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => Date: Thu, 2 Jul 2026 19:30:30 +0200 Subject: [PATCH 4/5] fix: draw distance regression (#3699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes
iOS
Before After
Android
Before After
## ๐Ÿงช Testing ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- package/src/components/MessageList/MessageFlashList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/src/components/MessageList/MessageFlashList.tsx b/package/src/components/MessageList/MessageFlashList.tsx index 46c99b714e..8eebd18cb1 100644 --- a/package/src/components/MessageList/MessageFlashList.tsx +++ b/package/src/components/MessageList/MessageFlashList.tsx @@ -1132,7 +1132,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => Date: Thu, 2 Jul 2026 14:01:14 -0500 Subject: [PATCH 5/5] fix: support expo-media-library 56.0.5+ (#3700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal ``` node_modules/expo-media-library/CHANGELOG.md, version 56.0.5 โ€” 2026-05-20: ๐Ÿ›  Breaking changes Promote the object-oriented MediaLibrary API to the root expo-media-library import and move the legacy API to expo-media-library/legacy. (#46030 by @Wenszel) ``` ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes before: https://github.com/user-attachments/assets/fa60387f-d9c6-4272-89f2-db35225ea59c after: https://github.com/user-attachments/assets/3551235c-430c-4a91-9343-a4af8d177d2c ## ๐Ÿงช Testing Tested on both iOS and Android ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- .../src/optionalDependencies/getLocalAssetUri.ts | 8 ++++++-- .../expo-package/src/optionalDependencies/getPhotos.ts | 8 ++++++-- .../optionalDependencies/iOS14RefreshGallerySelection.ts | 8 ++++++-- .../oniOS14GalleryLibrarySelectionChange.ts | 8 ++++++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/package/expo-package/src/optionalDependencies/getLocalAssetUri.ts b/package/expo-package/src/optionalDependencies/getLocalAssetUri.ts index 5f51479f79..362e0904da 100644 --- a/package/expo-package/src/optionalDependencies/getLocalAssetUri.ts +++ b/package/expo-package/src/optionalDependencies/getLocalAssetUri.ts @@ -3,9 +3,13 @@ import { Platform } from 'react-native'; let MediaLibrary; try { - MediaLibrary = require('expo-media-library'); + MediaLibrary = require('expo-media-library/legacy'); } catch (e) { - // do nothing + try { + MediaLibrary = require('expo-media-library'); + } catch (e) { + // do nothing + } } if (!MediaLibrary) { diff --git a/package/expo-package/src/optionalDependencies/getPhotos.ts b/package/expo-package/src/optionalDependencies/getPhotos.ts index 0e4f2bd728..366ddc112d 100644 --- a/package/expo-package/src/optionalDependencies/getPhotos.ts +++ b/package/expo-package/src/optionalDependencies/getPhotos.ts @@ -10,9 +10,13 @@ import { getLocalAssetUri } from './getLocalAssetUri'; let MediaLibrary; try { - MediaLibrary = require('expo-media-library'); + MediaLibrary = require('expo-media-library/legacy'); } catch (e) { - // do nothing + try { + MediaLibrary = require('expo-media-library'); + } catch (e) { + // do nothing + } } if (!MediaLibrary) { diff --git a/package/expo-package/src/optionalDependencies/iOS14RefreshGallerySelection.ts b/package/expo-package/src/optionalDependencies/iOS14RefreshGallerySelection.ts index 49bfe503cc..fd0dff44e4 100644 --- a/package/expo-package/src/optionalDependencies/iOS14RefreshGallerySelection.ts +++ b/package/expo-package/src/optionalDependencies/iOS14RefreshGallerySelection.ts @@ -3,9 +3,13 @@ import { Platform } from 'react-native'; let MediaLibrary; try { - MediaLibrary = require('expo-media-library'); + MediaLibrary = require('expo-media-library/legacy'); } catch (e) { - // do nothing + try { + MediaLibrary = require('expo-media-library'); + } catch (e) { + // do nothing + } } if (!MediaLibrary) { diff --git a/package/expo-package/src/optionalDependencies/oniOS14GalleryLibrarySelectionChange.ts b/package/expo-package/src/optionalDependencies/oniOS14GalleryLibrarySelectionChange.ts index 89721ac6a3..de2be0f0a5 100644 --- a/package/expo-package/src/optionalDependencies/oniOS14GalleryLibrarySelectionChange.ts +++ b/package/expo-package/src/optionalDependencies/oniOS14GalleryLibrarySelectionChange.ts @@ -3,9 +3,13 @@ import { Platform } from 'react-native'; let MediaLibrary; try { - MediaLibrary = require('expo-media-library'); + MediaLibrary = require('expo-media-library/legacy'); } catch (e) { - // do nothing + try { + MediaLibrary = require('expo-media-library'); + } catch (e) { + // do nothing + } } if (!MediaLibrary) {