> ## 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 JavaScript SDK.

> [accordion] AI Integration Quick Reference
>
> ```javascript theme={null}
> // Pass a fetch listener to get up to two phases: cached first (instant), then server.
> const request = new CometChat.MessagesRequestBuilder().setUID("UID").setLimit(30).build();
>
> request
>   .fetchPrevious(
>     new CometChat.MessageFetchListener({
>       onCachedMessages: (messages) => { /* Phase 1: local cache. Render immediately. */ },
>       onServerMessages: (messages) => { /* Phase 2: server. Reconcile, don't append. Never fires offline. */ },
>       onError: (error) => { /* Offline ends here, after the cached page, with FAILED_TO_FETCH. */ },
>     })
>   )
>   .catch(() => {}); // the returned Promise also rejects offline — handle it
>
> // Single items (additive two-phase overloads)
> CometChat.getConversation("UID", "user", new CometChat.ConversationDetailsListener({ /* … */ }));
> CometChat.getMessageDetails(messageId, new CometChat.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 fetch listener as the optional argument and each read fires **up to two** callbacks, always in this order:

**1. Cached phase**

The SDK delivers a page from the local cache **first**. It normally fires **even when the page is empty** — an empty cached page is indistinguishable from a cache miss, by design. Render it right away. Some reads skip this phase entirely, such as a blocked-users read with a search keyword; see [When the cached page is empty](#when-the-cached-page-is-empty).

**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: their server page is delivered but not written to the cache. The cached blocked-users list is kept current by the SDK itself.)

Three rules follow from this:

* **Offline, the read ends with an error.** After the cached page, a read whose request gets no answer settles with `onError` carrying code `FAILED_TO_FETCH`. A real server error reaches `onError` unchanged. The call always settles. Start the next fetch on a request object only after the previous one has returned: a second fetch started while one is running gets no cached page, and for messages, threads, reactions and the notification feed it ends with `onError` with code `REQUEST_IN_PROGRESS`.
* **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.
* **The Promise still settles too.** Passing a listener does not replace the returned Promise: it settles on the server outcome, so it also **rejects** offline. If you use only the listener, add a `.catch()` to avoid an unhandled rejection. Consume the result from one channel, not both.

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

## Read messages

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

> [tab] TypeScript
>
> ```typescript theme={null}
> const request: CometChat.MessagesRequest = new CometChat.MessagesRequestBuilder()
>   .setUID("UID")
>   .setLimit(30)
>   .build();
>
> request
>   .fetchPrevious(
>     new CometChat.MessageFetchListener({
>       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);
>       },
>     })
>   )
>   .catch(() => {});
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> const request = new CometChat.MessagesRequestBuilder()
>   .setUID("UID")
>   .setLimit(30)
>   .build();
>
> request
>   .fetchPrevious(
>     new CometChat.MessageFetchListener({
>       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);
>       },
>     })
>   )
>   .catch(() => {});
> ```

## Read conversations

`ConversationsRequest` pages with `fetchNext`, taking a `ConversationFetchListener`.

> [tab] TypeScript
>
> ```typescript theme={null}
> const request: CometChat.ConversationsRequest = new CometChat.ConversationsRequestBuilder()
>   .setLimit(30)
>   .build();
>
> request
>   .fetchNext(
>     new CometChat.ConversationFetchListener({
>       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);
>       },
>     })
>   )
>   .catch(() => {});
> ```

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

## Read the other lists

Every remaining list read accepts the same two-phase listener. Each listener is constructed from an object with 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` | `onCachedThreads` / `onServerThreads` |
| `UsersRequest` | `fetchNext` | `UserFetchListener` | `onCachedUsers` / `onServerUsers` |
| `BlockedUsersRequest` | `fetchNext` | `UserFetchListener` | `onCachedUsers` / `onServerUsers` |
| `GroupsRequest` | `fetchNext` | `GroupFetchListener` | `onCachedGroups` / `onServerGroups` |
| `GroupMembersRequest` | `fetchNext` | `GroupMemberFetchListener` | `onCachedGroupMembers` / `onServerGroupMembers` |
| `BannedMembersRequest` | `fetchNext` | `BannedMemberFetchListener` | `onCachedBannedGroupMembers` / `onServerBannedGroupMembers` |
| `ReactionsRequest` | `fetchPrevious`, `fetchNext` | `ReactionFetchListener` | `onCachedReactions` / `onServerReactions` |
| `NotificationFeedRequest` | `fetchNext` | `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(
>     new CometChat.UserFetchListener({
>       onCachedUsers: (users: CometChat.User[]) => { /* render */ },
>       onServerUsers: (users: CometChat.User[]) => { /* reconcile, keyed by uid */ },
>       onError: (error: CometChat.CometChatException) => console.log(error.code),
>     })
>   )
>   .catch(() => {});
> ```

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

> [!NOTE]
>
> For `ReactionsRequest`, use one request object per direction: an object caches only the first direction it fetches, and its cached phase stays empty in the other direction. Users, groups, members, banned members, reactions, and the notification feed are served from a recorded copy of each request (up to `maxSnapshotQueries` distinct requests, each kept for `snapshotTtlDays`), so a list you never opened online is empty offline. See the [Configuration & API Reference](https://www.cometchat.com/docs/sdk/javascript/offline-reference.md).

## Read a single item

`getConversation` and `getMessageDetails` each accept a two-phase listener as a final argument. Unlike the list reads, the cached callback fires **only when the item is cached** — never with an empty or null object. A cache miss goes straight to the server phase online, or straight to `onError` offline.

> [tab] TypeScript
>
> ```typescript theme={null}
> CometChat.getConversation(
>   "UID",
>   CometChat.RECEIVER_TYPE.USER,
>   new CometChat.ConversationDetailsListener({
>     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),
>   })
> ).catch(() => {});
>
> CometChat.getMessageDetails(
>   messageId,
>   new CometChat.MessageDetailsListener({
>     onCachedMessage: (message: CometChat.BaseMessage) => { /* cache hit only */ },
>     onServerMessage: (message: CometChat.BaseMessage) => { /* server copy */ },
>     onError: (error: CometChat.CometChatException) => console.log(error.code),
>   })
> ).catch(() => {});
> ```

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

## 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 can be computed locally**. A single server-only filter makes the whole cached phase come back empty (or not fire at all), 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 `setSearchKeyword`, which blanks the cached phase on any request that carries a search keyword (threads, reactions and the notification feed never carry one). Other examples are `hasLinks`, `updatesOnly` and `setInteractionGoalCompletedOnly` on `MessagesRequest`, setting a `UID` and a `GUID` together (one sender's messages in a group), and the status filters (`UsersRequest.setStatus`, `GroupMembersRequest.setStatus`) on the entity lists. `setUpdatedAfter` is a paging input, not a filter.

> [!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 is in the [Configuration & API Reference](https://www.cometchat.com/docs/sdk/javascript/offline-reference.md#cache-supported-filters).


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