> ## Documentation Index
> Fetch the complete documentation index at: https://www.cometchat.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Show Cached Data

> Render cached messages, conversations, and lists instantly and reconcile them against the server with the CometChat React Native Chat SDK.

> [accordion] AI Integration Quick Reference
>
> | Field | Value |
> | - | - |
> | Applies to | `fetchNext()` / `fetchPrevious()` on every request class, plus `getConversation()` and `getMessageDetails()` |
> | How to opt in | Pass a listener. A read with no listener gets the server result only. |
> | Order | `onCached…` (instant, may be empty) → `onServer…` (replaces the same slice) |
> | Offline | After the cached page the read ends with `onError` (`FAILED_TO_FETCH`) and the Promise rejects |
> | Prerequisites | [Offline support enabled](https://www.cometchat.com/docs/sdk/react-native/offline-enable.md) |
>
> ```typescript theme={null}
> const request = new CometChat.MessagesRequestBuilder().setUID("UID").setLimit(30).build();
>
> request.fetchPrevious({
>   onCachedMessages: (messages) => { /* Phase 1: local cache. Render immediately. */ },
>   onServerMessages: (messages) => { /* Phase 2: server. Reconcile, don't append. */ },
>   onError: (error) => { /* Offline ends here, after the cached page. */ },
> });
>
> CometChat.getConversation("UID", CometChat.RECEIVER_TYPE.USER, conversationDetailsListener);
> CometChat.getMessageDetails(messageId, messageDetailsListener);
> ```

With offline support enabled, every list and lookup the SDK already offers can answer from the on-device cache first and then from the server. Your UI paints instantly from the cache, then reconciles when the server replies. This page describes that contract, the reads it applies to, and how to merge the two phases.

## The two-phase contract

Pass a listener to a read and it fires **up to two** callbacks, always in this order:

**1. Cached phase**

The SDK delivers a page from the local cache **first**. It fires **even when the page is empty** — an empty cached page is indistinguishable from a cache miss, by design. Render it right away.

**2. Server phase**

After a successful server response, the SDK delivers the server's copy for the **same slice** and writes it through to the cache before the callback. Reconcile your displayed list against it. (Blocked users are the exception: a read of the blocked-users list delivers the server page but does not write it to the cache. The SDK keeps that list current with its own background refresh instead.)

Three rules follow from this:

* **The Promise is unchanged.** The read still returns a Promise that settles on the **server** result. The cached page never settles it, and a read with no listener receives no cached phase. Consume the final result from one channel — the Promise or the `onServer…` callback.
* **Offline, the read ends with an error.** After the cached page, a read that gets no answer because the device is offline settles with `onError` and rejects the Promise with the SDK's not-connected error, code `FAILED_TO_FETCH`. A real server error reaches `onError` and the Promise unchanged. The call always settles. Wait for each fetch to finish before you start the next one on the same request object: an overlapping call gets no cached page and behaves as it did without offline support. For messages, threads, reactions and the notification feed it fails with a request-in-progress error. For users, groups, members and conversations it returns an empty list.
* **The server phase never fires offline.** It fires only on a genuine server response. Never treat a cached page as "end of history"; an empty cached page only means the end of the *cached* window.

> [!NOTE]
>
> With offline support **off**, these listeners behave like a plain read: the cached phase never fires, and the server result arrives in `onServer…` alongside the Promise. Nothing changes until you enable offline ([Enable Offline Support](https://www.cometchat.com/docs/sdk/react-native/offline-enable.md)). Requires SDK **4.2.0**.

## Read messages

`MessagesRequest` supports both directions. `fetchPrevious` pages toward older messages; `fetchNext` pages toward newer ones. Both accept a `MessageFetchListener`.

> [tab] TypeScript
>
> ```typescript theme={null}
> const request: CometChat.MessagesRequest = new CometChat.MessagesRequestBuilder()
>   .setUID("UID")
>   .setLimit(30)
>   .build();
>
> request.fetchPrevious({
>   onCachedMessages: (messages: CometChat.BaseMessage[]) => {
>     // Render the cached page immediately.
>   },
>   onServerMessages: (messages: CometChat.BaseMessage[]) => {
>     // Reconcile this slice against the cached page, keyed by message id.
>   },
>   onError: (error: CometChat.CometChatException) => {
>     console.log("Fetch failed:", error.code);
>   },
> });
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> const request = new CometChat.MessagesRequestBuilder()
>   .setUID("UID")
>   .setLimit(30)
>   .build();
>
> request.fetchPrevious({
>   onCachedMessages: (messages) => {
>     // Render the cached page immediately.
>   },
>   onServerMessages: (messages) => {
>     // Reconcile this slice against the cached page, keyed by message id.
>   },
>   onError: (error) => {
>     console.log("Fetch failed:", error.code);
>   },
> });
> ```

## Read conversations

`ConversationsRequest` pages in one direction with `fetchNext`, accepting a `ConversationFetchListener`.

> [tab] TypeScript
>
> ```typescript theme={null}
> const request: CometChat.ConversationsRequest = new CometChat.ConversationsRequestBuilder()
>   .setLimit(30)
>   .build();
>
> request.fetchNext({
>   onCachedConversations: (conversations: CometChat.Conversation[]) => {
>     // Render the cached list immediately.
>   },
>   onServerConversations: (conversations: CometChat.Conversation[]) => {
>     // Reconcile, keyed by conversation id.
>   },
>   onError: (error: CometChat.CometChatException) => {
>     console.log("Fetch failed:", error.code);
>   },
> });
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> const request = new CometChat.ConversationsRequestBuilder()
>   .setLimit(30)
>   .build();
>
> request.fetchNext({
>   onCachedConversations: (conversations) => {
>     // Render the cached list immediately.
>   },
>   onServerConversations: (conversations) => {
>     // Reconcile, keyed by conversation id.
>   },
>   onError: (error) => {
>     console.log("Fetch failed:", error.code);
>   },
> });
> ```

## Read the other lists

Every remaining list read accepts the same optional listener. Each listener has cached/server callbacks of the matching type plus `onError`.

| Request | Fetch method(s) | Listener | Cached / server callback |
| - | - | - | - |
| `MessagesRequest` | `fetchPrevious`, `fetchNext` | `MessageFetchListener` | `onCachedMessages` / `onServerMessages` |
| `ConversationsRequest` | `fetchNext` | `ConversationFetchListener` | `onCachedConversations` / `onServerConversations` |
| `ThreadsRequest` | `fetchNext` | `ThreadFetchListener` (alias `ThreadsFetchListener`) | `onCachedThreads` / `onServerThreads` |
| `UsersRequest` | `fetchNext` | `UserFetchListener` | `onCachedUsers` / `onServerUsers` |
| `BlockedUsersRequest` | `fetchNext` | `UserFetchListener` | `onCachedUsers` / `onServerUsers` |
| `GroupsRequest` | `fetchNext` | `GroupFetchListener` | `onCachedGroups` / `onServerGroups` |
| `GroupMembersRequest` | `fetchNext` | `GroupMemberFetchListener` | `onCachedGroupMembers` / `onServerGroupMembers` |
| `BannedMembersRequest` | `fetchNext` | `BannedGroupMemberFetchListener` | `onCachedBannedGroupMembers` / `onServerBannedGroupMembers` |
| `ReactionsRequest` | `fetchPrevious`, `fetchNext` | `ReactionFetchListener` | `onCachedReactions` / `onServerReactions` |
| `NotificationFeedRequest` | `fetchNext` | `FeedItemFetchListener` (alias `NotificationFeedFetchListener`) | `onCachedNotificationFeedItems` / `onServerNotificationFeedItems` |

The call shape is identical to the conversations example above — swap the request, the listener, and the callback names.

> [tab] TypeScript
>
> ```typescript theme={null}
> const request: CometChat.UsersRequest = new CometChat.UsersRequestBuilder()
>   .setLimit(30)
>   .build();
>
> request.fetchNext({
>   onCachedUsers: (users: CometChat.User[]) => { /* render */ },
>   onServerUsers: (users: CometChat.User[]) => { /* reconcile, keyed by uid */ },
>   onError: (error: CometChat.CometChatException) => console.log(error.code),
> });
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> const request = new CometChat.UsersRequestBuilder()
>   .setLimit(30)
>   .build();
>
> request.fetchNext({
>   onCachedUsers: (users) => { /* render */ },
>   onServerUsers: (users) => { /* reconcile, keyed by uid */ },
>   onError: (error) => console.log(error.code),
> });
> ```

> [!NOTE]
>
> For `ReactionsRequest`, use one request object per direction. Each direction is cached under its own key, but a request object keeps a single page counter, so one object used in both directions replays the wrong pages. Users, groups, members, banned members, reactions, and the notification feed are served from a per-request snapshot rather than a shared store; the number of snapshots and how long they stay valid are set by `maxSnapshotQueries` and `snapshotTtlDays` in the [Configuration & API Reference](https://www.cometchat.com/docs/sdk/react-native/offline-reference.md).

## Read a single item

`getConversation` and `getMessageDetails` each accept an optional listener as a last argument. Unlike the list reads, the cached callback fires **only when the item is cached** — never with an empty or undefined object. A cache miss goes straight to the server phase online, or straight to `onError` offline. The Promise still settles on the server result.

> [tab] TypeScript
>
> ```typescript theme={null}
> CometChat.getConversation("UID", CometChat.RECEIVER_TYPE.USER, {
>   onCachedConversation: (conversation?: CometChat.Conversation) => {
>     // Fires only on a cache hit.
>   },
>   onServerConversation: (conversation: CometChat.Conversation) => {
>     // Server copy, written through before delivery.
>   },
>   onError: (error: CometChat.CometChatException) => {
>     console.log("Lookup failed:", error.code);
>   },
> });
>
> CometChat.getMessageDetails(messageId, {
>   onCachedMessage: (message?: CometChat.BaseMessage) => { /* cache hit only */ },
>   onServerMessage: (message: CometChat.BaseMessage) => { /* server copy */ },
>   onError: (error: CometChat.CometChatException) => console.log(error.code),
> });
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> CometChat.getConversation("UID", CometChat.RECEIVER_TYPE.USER, {
>   onCachedConversation: (conversation) => {
>     // Fires only on a cache hit.
>   },
>   onServerConversation: (conversation) => {
>     // Server copy, written through before delivery.
>   },
>   onError: (error) => {
>     console.log("Lookup failed:", error.code);
>   },
> });
>
> CometChat.getMessageDetails(messageId, {
>   onCachedMessage: (message) => { /* cache hit only */ },
>   onServerMessage: (message) => { /* server copy */ },
>   onError: (error) => console.log(error.code),
> });
> ```

The single-conversation listener is `ConversationDetailsListener`; the single-message listener is `MessageDetailsListener`.

## Merge the two phases

The server phase **replaces the same slice** — it is a reconcile, not a second batch. Render the cached page straight away, then on the server phase diff it into your displayed list keyed by the item's stable id (message id, conversation id, uid, guid). Do not append the server page to the cached one.

> [!TIP]
>
> Key your list by id so the two phases merge cleanly: update items that changed, add new ones, and drop cached items the server slice omits. To page further back, issue the next `fetchPrevious()` / `fetchNext()` rather than treating the server phase as more pages.

## When the cached page is empty

The cache answers a read **only if every filter on it is locally computable**. A single server-only filter makes the entire cached phase return an empty list — with no error and no warning — while the server phase still answers in full. The guiding rule is *better empty than wrong*.

The most common server-only filter is `searchKeyword`, which blanks the cached phase on any request that carries a search keyword (threads, reactions and the notification feed have none). Other examples on `MessagesRequest` are `hasLinks`, `updatesOnly`, `interactionGoalCompletedOnly`, and setting both `UID` and `GUID` outside a thread (one sender's messages in a group). Also, `hideMessagesFromBlockedUsers` returns an empty cached page until the blocked-users list has been fetched from the server once. Presence filters such as `status` on the user and member lists also fall back to the server.

> [!WARNING]
>
> An offline-critical screen must use only cache-supported filters — one innocuous builder flag can silently disable the cached phase for a whole screen. The full per-builder matrix lives in the [Configuration & API Reference](https://www.cometchat.com/docs/sdk/react-native/offline-reference.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.