This is beta documentation for Stream Chat React Native SDK v9. For the latest stable version, see the latest version (v8) .

Message Reminders

Message reminders let users revisit important messages later. With a timestamp, the user receives a notification at the scheduled time. Without one, the reminder acts like a bookmark.

Best Practices

  • Enable Push V3 and the reminder event before testing reminder delivery.
  • Keep reminder offsets limited to reduce UI clutter.
  • Use useMessageReminder for per-message updates instead of manual polling.
  • Sort reminders by remind_at for predictable list ordering.
  • Clear reminder indicators when a reminder is deleted or expired.

Push Notifications Setup

Reminders require Push V3. In the Stream Dashboard, go to Push Notifications and click Upgrade to V3.

Push V3

Then, in each configuration, enable notification.reminder_due in Configure Push Notification Templates.

Message Reminder UI Interaction

Users create, update, or delete reminders from the message actions menu.

The reminder indicator appears in the message UI (or disappears when deleted).

Get Message Reminder Data

The SDK uses client.reminders for CRUD. It stores a reactive map of message IDs to Reminder instances. Each Reminder extends ReminderResponse with timeLeftMs.

export type ReminderState = {
  channel_cid: string;
  created_at: Date;
  message: MessageResponse | null;
  message_id: string;
  remind_at: Date | null;
  timeLeftMs: number | null;
  updated_at: Date;
  user: UserResponse | null;
  user_id: string;
};
  1. Get a reminder for a specific message

Use useMessageReminder to get the Reminder instance for a message ID.

You can subscribe at two levels:

  1. Subscribe to a specific reminder’s state
import { useMessageReminder, useStateStore } from "stream-chat-react-native";
import type { LocalMessage, ReminderState } from "stream-chat";

const reminderStateSelector = (state: ReminderState) => ({
  timeLeftMs: state.timeLeftMs,
});

const Component = ({ message }: { message: LocalMessage }) => {
  // access the message reminder instance
  const reminder = useMessageReminder(message.id);
  const { timeLeftMs } =
    useStateStore(reminder?.state, reminderStateSelector) ?? {};
};

timeLeftMs updates more frequently as the deadline approaches (daily → hourly → minutely) and less frequently afterward.

  1. Subscribe to reminders pagination

Pagination is handled by RemindersPaginator. Use useQueryReminders to fetch pages and react to updates and deletions.

Pagination returns ReminderResponse objects, not Reminder instances.

import { useCallback, useEffect } from "react";
import { ActivityIndicator, FlatList, Text, View } from "react-native";
import type { ReminderResponse } from "stream-chat";
import { useChatContext, useQueryReminders } from "stream-chat-react-native";
import { ReminderItem } from "./ReminderItem";

const renderItem = ({ item }: { item: ReminderResponse }) => (
  <ReminderItem {...item} />
);

const renderEmptyComponent = <Text>No reminders available</Text>;

const RemindersList = () => {
  const { client } = useChatContext();
  const { data, isLoading, loadNext } = useQueryReminders();

  useEffect(() => {
    client.reminders.paginator.filters = {};
    client.reminders.paginator.sort = { remind_at: 1 };
  }, [client.reminders]);

  const renderFooter = useCallback(() => {
    if (isLoading) {
      return (
        <ActivityIndicator size={"small"} style={{ marginVertical: 16 }} />
      );
    }
  }, [isLoading]);

  return (
    <View style={{ flex: 1 }}>
      <FlatList
        contentContainerStyle={{ flexGrow: 1 }}
        data={data}
        keyExtractor={(item) => item.message.id}
        renderItem={renderItem}
        ListEmptyComponent={renderEmptyComponent}
        ListFooterComponent={renderFooter}
        onEndReached={loadNext}
      />
    </View>
  );
};

Message Reminder Configuration

Configure which reminder offsets users can select:

const minute = 60 * 1000;
client.reminders.updateConfig({
  scheduledOffsetsMs: [30 * minute, 60 * minute],
});

You can also set when the reminder stops refreshing “time since due”:

const day = 24 * 60 * 60 * 1000;
client.reminders.updateConfig({
  stopTimerRefreshBoundaryMs: day,
});

Reminder indicator on message

You can customize reminder and saved-for-later indicators by passing dedicated header overrides to Channel:

Message Reminder Header

import {
  Time,
  useMessageContext,
  useMessageReminder,
  useStateStore,
  useTranslationContext,
} from "stream-chat-react-native";
import { ReminderState } from "stream-chat";
import { StyleSheet, Text, View } from "react-native";

const reminderStateSelector = (state: ReminderState) => ({
  timeLeftMs: state.timeLeftMs,
});

export const CustomMessageReminderHeader = () => {
  const { message } = useMessageContext();
  const messageId = message?.id ?? "";
  const reminder = useMessageReminder(messageId);
  const { timeLeftMs } =
    useStateStore(reminder?.state, reminderStateSelector) ?? {};
  const { t } = useTranslationContext();

  const stopRefreshBoundaryMs = reminder?.timer.stopRefreshBoundaryMs;
  const stopRefreshTimeStamp =
    reminder?.remindAt && stopRefreshBoundaryMs
      ? reminder?.remindAt.getTime() + stopRefreshBoundaryMs
      : undefined;

  const isBehindRefreshBoundary =
    !!stopRefreshTimeStamp && new Date().getTime() > stopRefreshTimeStamp;

  if (!reminder) {
    return null;
  }

  if (reminder.remindAt && timeLeftMs != null) {
    return (
      <View style={styles.headerContainer}>
        <Time height={16} width={16} />
        <Text style={styles.headerTitle}>
          {isBehindRefreshBoundary
            ? t("Due since {{ dueSince }}", {
                dueSince: t("timestamp/ReminderNotification", {
                  timestamp: reminder.remindAt,
                }),
              })
            : t("Due {{ timeLeft }}", {
                timeLeft: t("duration/Message reminder", {
                  milliseconds: timeLeftMs,
                }),
              })}
        </Text>
      </View>
    );
  }

  return null;
};

export const CustomMessageSavedForLaterHeader = () => {
  const { t } = useTranslationContext();

  return (
    <View>
      <Text style={styles.headerTitle}>{t("Saved For Later")}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  headerContainer: {
    flexDirection: "row",
    alignItems: "center",
  },
  headerTitle: {
    fontSize: 14,
    fontWeight: "500",
    marginLeft: 4,
  },
});

<Channel
  MessageReminderHeader={CustomMessageReminderHeader}
  MessageSavedForLaterHeader={CustomMessageSavedForLaterHeader}
/>;