Repository navigation
Description
Issue
With offline support enabled, channels hydrated from the local DB end up with messagePagination.hasPrev = false even when the server has many older messages. Scrolling up in the MessageList never calls the server (loadMore returns early on !hasPrev), so the visible history is stuck at whatever is cached (24 messages at most).
There are two causes:
1. hasPrev is inferred from the local cache (stream-chat, client.hydrateActiveChannels)
ChannelManager.queryChannels first hydrates channels from the DB with:
this.client.hydrateActiveChannels(channelsFromDB, {
offlineMode: true,
skipInitialization: [],
}); // no queryChannelsOptionsThe pagination is then computed with messageSetPagination against DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE (25). Since the DB returns fewer messages than that, hasPrev is set to false.
That inference ("the page came back shorter than requested, so there is nothing older") is valid for a server response, but not for the local cache: the DB only holds the messages this device has already downloaded, which is often less than a page even when the channel has a long history (e.g. a channel only seen in the channel list has message_limit messages stored).
The online queryChannels response that follows does not correct it. With enableOfflineSupport, usePaginatedChannels passes skipInitialization: undefined, so the server page is merged into the existing message set, and messagePaginationLinear only updates hasPrev when the first message of the returned page is also the first message of the set. With 11–24 cached messages and message_limit: 10, that is never the case, so hasPrev stays false for the whole session.
2. Off-by-one in the offline query (stream-chat-react-native, src/store/apis/queries/selectMessagesForChannels.ts)
WHERE RowNum < 25ROW_NUMBER() starts at 1, so this returns 24 messages per channel, which is always below the page size of 25 used above. This is already fixed in 10.0.0-rc.2 (RowNum <= 25), but it is still present in 9.10.1 and 9.10.2-beta.2.
Fixing only the off-by-one is not enough: a channel with fewer than 25 cached messages still ends up with hasPrev = false.
Workaround we are using (patch on stream-chat 9.53.0, inside hydrateActiveChannels, right after the pagination is computed):
// local cache may hold fewer messages than the server; let the
// first loadMore (server response) decide whether older messages exist.
if (offlineMode && channelState.messages.length > 0) {
updatedMessagesSet.pagination.hasPrev = true;
}ChannelManager is the only caller of hydrateActiveChannels with offlineMode: true, so this only affects DB hydration. The trade-off is that, for a channel that is already fully cached, reaching the top triggers one extra loadMore that returns an empty page, which then sets hasPrev = false correctly.
Steps to reproduce
Steps to reproduce the behavior:
- Enable offline support (
<Chat enableOfflineSupport>) and render aChannelList. - Use a channel with more than 50 messages. Open it while online and scroll up until at least one older page is loaded, so more than 24 of its messages get stored in the local DB. Then leave it.
- Kill the app and open it again (online).
- Open that channel from the channel list and scroll up.
- Only the cached messages (24 at most) are shown, and no request for older messages is made. At this point
channel.state.messagePagination.hasPrevisfalse,channel.initializedistrueandchannel.offlineModeisfalse.
It is intermittent: it depends on whether the channel state comes from the DB or from the server first, and on how many messages of that channel are cached.
If 10 or fewer messages of the channel are cached (e.g. it was only seen in the channel list), the online queryChannels page covers the whole cached set, hasPrev gets recomputed, and the bug does not show up.
Expected behavior
A channel hydrated from the local DB should not be marked as having no older messages based on the size of the cache. Scrolling up should request older messages from the server, and the server response should decide whether the start of the channel was reached.
Project Related Information
Customization
Click To Expand
We use a copy of ChannelList's data layer (usePaginatedChannels) with the same skipInitialization: enableOfflineSupport ? undefined : ... logic as the SDK, plus the stock Channel and MessageList. The behavior reproduces with the stock components.
<Chat client={client} enableOfflineSupport>
<ChannelList
filters={filters}
sort={sort}
options={{ limit: 30, message_limit: 10 }}
/>
</Chat>Offline support
- I have enabled offline support.
- The feature I'm having does not occur when offline support is disabled. (stripe out if not applicable)
Environment
Click To Expand
package.json:
"react-native": "0.80.3",
"stream-chat": "9.53.0",
"stream-chat-react-native": "9.8.2"react-native info output:
react-native info
System:
OS: macOS 26.7.1
CPU: (11) arm64 Apple M3 Pro
Memory: 253.83 MB / 18.00 GB
Shell:
version: "5.9"
Binaries:
Node:
version: 22.23.4
Yarn:
version: 1.22.22
npm:
version: 10.9.9
Watchman: Not Found
Managers:
CocoaPods:
version: 1.16.2
SDKs:
iOS SDK:
Platforms:
- DriverKit 25.2
- iOS 26.2
- macOS 26.2
- tvOS 26.2
- visionOS 26.2
- watchOS 26.2
Android SDK: Not Found
IDEs:
Android Studio: 2025.2 AI-252.27397.103.2522.14617522
Xcode:
version: 26.2/17C52
Languages:
Java:
version: 17.0.15
Ruby:
version: 3.1.6
npmPackages:
"@react-native-community/cli":
installed: 19.1.2
wanted: 19.1.2
react:
installed: 19.1.0
wanted: 19.1.0
react-native:
installed: 0.80.3
wanted: 0.80.3
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: true
- Platform that you're experiencing the issue on:
- iOS
- Android
- iOS but have not tested behavior on Android
- Android but have not tested behavior on iOS
- Both
stream-chat-react-nativeversion you're using that has this issue:9.8.2(the same code is present in9.10.1and9.10.2-beta.2)
- Device/Emulator info:
- I am using a physical device
- OS version:
Android 15 - Device/Emulator:
Samsung Galaxy A55
Additional context
- New Architecture is enabled.
- In
10.0.0-rcthe same inference seems to remain:ChannelPaginatorhydrates from the DB without a requested limit, andMessageIntervalPaginator.pageReachedChannelStarttreats a page shorter than the default page size (25) as the start of the channel. With the off-by-one fixed, channels with 25+ cached messages would work, but channels with fewer cached messages would likely still be marked as having no older messages. We have not run v10, this is from reading the source. - Related: if the user opens the channel before the online
queryChannelsresponse arrives,Channelcallschannel.watch()withoutmessages.limit, soquery()assumesDEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE(100) while the server returns its own default page, which also yieldshasPrev = false.
Screenshots
Click To Expand
Both recordings follow the same steps on the same device: the channel was opened and paginated once while online (so more than 24 of its messages are stored in the local DB), the app was killed and reopened, and then the channel was opened from the channel list and scrolled up.
The loading indicator is a custom FooterComponent; the pagination logic is the stock SDK except for the proposed fix.
| Current behavior (stock SDK) | With the proposed fix |
|---|---|
History stops at the cached messages. hasPrev is false, so scrolling up never requests older messages. |
Older messages are requested from the server and the history loads page by page up to the first message. |
android-without-fixes.mov |
android-with-fixes.mp4 |
Activity
isekovanic commentedon Oct 28, 2026
Hi @Mathitarazonad ,
Thanks a bunch for opening a very detailed PR !
Yes, I noticed most of these things while working on V10 as well and they are consequently fixed there. There are a few rough edges that I'm resolving as we speak but most of the things should continue working there.
I'll also backport stuff to V9 too that should cover all of these scenarios directly. One sideeffect of the fixes will indeed be that what the offline DB loads for channels will always respect the provided
message_limit(if there) and only default to 25 as it is now. The reason for that is because this is in fact a bugfix for these particular scenarios as loading anything abovemessage_limitcan be very wrong.For example:
ChannelList)ChannelList)This will now display messages 25-16 followed by 10-1 (with 15-11 missing), which is incorrect. Only messages we're sure of should be displayed.
I'll let you know when I have something ready so you can give it a shot (probably in a beta release or so).
Mathitarazonad commentedon Oct 28, 2026
Hi @isekovanic, thank you for the quick and detailed response, we really appreciate it.
The trade-off you describe makes sense. Showing only messages that are known to be consistent is better than showing a history with gaps, especially when the app is opened offline.
In the meantime, we'll keep using a small patch until a version with the fix is available. We'd be glad to test the V9 beta and share the results here.
Thanks again on behalf of our team!
fix: read cached channels against the query's message_limit (#1958)
isekovanic commentedon Oct 28, 2026
Hi again @Mathitarazonad ,
The fix for this should now be available in version
stream-chat-react-native-9.11.1-beta.2.Would it be possible to try it out and see if it works for you ?
You can also check the PR for it for a full list of fixes it includes !
I'll close the ticket once you've confirmed stuff works on your end too.
fix: pass full request options to hydration query (#1959)
fix: respect message_limit when querying the db (#3908)
Mathitarazonad commentedon Oct 29, 2026
Hi @isekovanic!
We tested
9.11.1-beta.2and can confirm the fix works on our end. Channels hydrated from the offline DB now load older messages when scrolling up. With this, we'll be able to drop our local patch once the fix lands in a stable release.Since we've verified it works, we'll go ahead and close this issue. Thank you for the quick turnaround and for addressing everything so thoroughly.
Thanks again on behalf of our team!
isekovanic commentedon Oct 29, 2026
Cool ! Thanks for letting me know ! I'll cut an actual SDK release soon
stream-ci-bot commentedon Oct 29, 2026
🎉 This issue has been resolved in version 9.11.1 🎉
The release is available on:
v9.11.1Your semantic-release bot 📦🚀
fix: offline message limit pagination (GetStream#3907)