# Reminders and save for later

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

Reminders bring a message back to the user later. "Remind me" sets a time, and "Save for later" bookmarks the message with no time. Both are one feature in the API: a reminder with or without a `remind_at`.

## What users get

A message with a reminder shows a line above it. With a time, it reads "Reminder set" and how long until it's due, such as "in 30 minutes", and the time counts down as it approaches. Once the time has passed, it reads "Reminder overdue" and how long ago it was due. A message saved for later reads "Saved For Later".

The default message menu has no reminder actions. Your app adds them, for example Remind me, Save for later and Remove reminder; [Reminder actions](#reminder-actions) builds them. A message has one reminder at most, so while it has one, the menu should offer only its removal.

Reminders belong to the user, not to the device: their reminders show on every device they're signed in on.

## How it works across the layers

- **State.** `client.reminders` is the reminder manager. It keeps one `Reminder` per message, keyed by the message id, and talks to the reminders API. Its `paginator` is the reminder list, the user's reminders across all channels.
- **UI.** `MessageHeader` looks up the message's reminder with `useMessageReminder(message.id)`. When there is one, it renders `MessageReminderHeader` for a reminder with a time, and `MessageSavedForLaterHeader` for one without. `Chat` starts the manager's event subscriptions and timers when it mounts and stops them when it unmounts.
- **Navigation.** Not involved.

The manager's `state` store holds a `Map` from message id to `Reminder`. Several things fill it:

- the messages a channel query returns carry their `reminder` field, so opening a channel brings in the reminders on its messages;
- the `reminder.created`, `reminder.updated` and `reminder.deleted` events, so a reminder set on another device shows here too;
- `message.deleted` removes the message's reminder, and `message.undeleted` restores it;
- every page the reminder list loads;
- your own calls: `client.reminders.createReminder()`, `client.reminders.updateReminder()`, `client.reminders.upsertReminder()` and `client.reminders.deleteReminder()`.

None of these calls is optimistic. The state changes once the server answers:

```ts
// Remind me in an hour
await client.reminders.upsertReminder(
  { message_id: message.id },
  { remind_at: new Date(Date.now() + 60 * 60 * 1000) },
);

// Save for later: a reminder without a time
await client.reminders.createReminder({ message_id: message.id });

// Remove either kind
await client.reminders.deleteReminder(message.id);
```

`client.reminders.upsertReminder()` updates the message's reminder when it has one and creates it otherwise.

With offline support, reminders are stored with their messages in the local database.

### The reminder list

`client.reminders.paginator` queries the user's reminders across channels. Its `state` store has `items`, `isLoading`, `hasMoreTail` and `lastQueryError`, like the other paginators in the state layer; [How pagination works](https://getstream.io/chat/docs/sdk/react-native/v10/state/loading-data/how-pagination-works/) explains them. The items are reminder records from the server, each with its `message` and `channel`, rather than `Reminder` instances. Every loaded page also goes into the manager's state.

For a "Saved for later" screen, filter to reminders without a time:

1. Set `paginator.filters` to `{ remind_at: { $exists: false } }`. Setting `filters` or `sort` empties the list.
2. Call `client.reminders.queryNextReminders()` for the first page, and again for each next page.
3. Call `ReminderPaginator.reload()` to load the list from the start.

The paginator holds what its queries returned. A reminder created or deleted afterwards changes the manager's state, but not the paginator until it reloads. `client.reminders.paginator` is one list per client, so screens that need different filters take turns with it.

`useQueryReminders()` reads the paginator and also follows reminder events, so a list built on it changes when a reminder is created, updated or deleted. The SDK has no reminder list component. [Your own reminder list](#your-own-reminder-list) builds one.

#### Reminder state and timers

Each `Reminder` has its own `state` store: the server fields (`message_id`, `channel_cid`, `remind_at`, `created_at`, `updated_at`, `user_id`, `message` and `user`) plus `timeLeftMs`. The timestamps are unix nanoseconds, as the API sends them; `nsToDate(reminder.remindAt)` turns one into a `Date`. `timeLeftMs` is in milliseconds and goes negative once the reminder is due. A reminder that is saved for later has `remind_at: null` and `timeLeftMs: null`.

A timer refreshes `timeLeftMs`, more often the closer the deadline is. Take a reminder set for three hours from now:

1. It refreshes once an hour until the deadline is an hour away.
2. Then once a minute, until the deadline is a minute away.
3. Then once more, at the deadline itself.
4. After the deadline, it slows down the same way: once a minute for the first hour, then once an hour for a day, then once a day.
5. Two weeks after the deadline, the timer stops. `stopTimerRefreshBoundaryMs` sets that boundary.

When a reminder comes due, the server sends a `notification.reminder_due` event, and a push notification when your push setup includes that event. The SDK doesn't act on the event: the message's header keeps counting, and nothing else happens. See [Push notifications](https://getstream.io/chat/docs/sdk/react-native/v10/features/app-experience/push-notifications/).

Reminder push notifications need Push V3 on your app, as [Message reminders](https://getstream.io/chat/docs/javascript/message-reminders/) says. Push is turned on per event type in the push templates, so turn it on for `notification.reminder_due` too, in the Dashboard or from your server. See [Push templates](https://getstream.io/chat/docs/node/push-template/).

## Turning it on or off

Reminders are on when both of these allow them:

- the channel type's `user_message_reminders` setting on the server, see [Message reminders](https://getstream.io/chat/docs/javascript/message-reminders/);
- `channel.userMessageReminders.enabled` in the client configuration, which defaults to `true`.

A server "no" wins. The resolved value is `channel.config.userMessageReminders.enabled`. The SDK shows the header of any reminder it has, so check the value before you offer reminder actions, as the sample below does.

To turn reminders off in the client for every channel:

```ts
client.config.set({ channel: { userMessageReminders: { enabled: false } } });
```

[Configuration](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/configuration/) explains `client.config` and how it combines with the server settings.

## Customizing

### Reminder actions

Add the actions through `messageActions` on `Channel` and `Thread`. The actions are built when the menu opens, so reading the reminder with `client.reminders.getFromState()` gets the current one:

```tsx
import { useCallback } from "react";
import type { Channel as StreamChannel } from "stream-chat";
import type { MessageActionsParams } from "stream-chat-react-native";
import {
  Bell,
  Bookmark,
  Channel,
  MessageComposer,
  MessageList,
  messageActions,
  useChatContext,
  useTheme,
} from "stream-chat-react-native";

const hour = 60 * 60 * 1000;

export const ChannelWithReminders = ({
  channel,
}: {
  channel: StreamChannel;
}) => {
  const { client } = useChatContext();
  const {
    theme: { semantics },
  } = useTheme();

  const withReminders = useCallback(
    (params: MessageActionsParams) => {
      const actions = messageActions(params);
      if (params.error || !channel.config.userMessageReminders.enabled) {
        return actions;
      }

      const messageId = params.message.id;
      const reminder = client.reminders.getFromState(messageId);
      const iconProps = {
        height: 20,
        stroke: semantics.textSecondary,
        width: 20,
      };
      const report = (error: unknown) => console.warn(error);

      if (reminder) {
        actions.push({
          action: () => {
            client.reminders.deleteReminder(messageId).catch(report);
          },
          actionType: "removeReminder",
          icon: <Bell {...iconProps} />,
          title:
            reminder.remindAt == null
              ? "Remove save for later"
              : "Remove reminder",
          type: "standard",
        });
        return actions;
      }

      actions.push(
        {
          action: () => {
            client.reminders
              .upsertReminder(
                { message_id: messageId },
                { remind_at: new Date(Date.now() + hour) },
              )
              .catch(report);
          },
          actionType: "remindMe",
          icon: <Bell {...iconProps} />,
          title: "Remind me in an hour",
          type: "standard",
        },
        {
          action: () => {
            client.reminders
              .createReminder({ message_id: messageId })
              .catch(report);
          },
          actionType: "saveForLater",
          icon: <Bookmark {...iconProps} />,
          title: "Save for later",
          type: "standard",
        },
      );
      return actions;
    },
    [channel, client, semantics],
  );

  return (
    <Channel channel={channel} messageActions={withReminders}>
      <MessageList />
      <MessageComposer />
    </Channel>
  );
};
```

To offer several times, open a picker of your own from the Remind me action. `client.reminders.scheduledOffsetsMs` holds the offsets to offer, in milliseconds from now: 2 minutes, 30 minutes, an hour, 2 hours, 8 hours and a day by default. A native `Alert` shows at most three buttons on Android, so use a bottom sheet or a menu for more.

### The offered times

`scheduledOffsetsMs` and the timer boundary are set in `client.config`:

```ts
const minute = 60 * 1000;

client.config.set({
  client: {
    reminders: {
      scheduledOffsetsMs: [15 * minute, 60 * minute, 24 * 60 * minute],
      // Keep refreshing overdue reminders for one day instead of two weeks
      stopTimerRefreshBoundaryMs: 24 * 60 * minute,
    },
  },
});
```

`client.reminders.updateConfig({ scheduledOffsetsMs })` changes the same settings on the manager directly.

### The reminder headers

Replace `MessageReminderHeader` and `MessageSavedForLaterHeader` through `WithComponents`. Read the message's reminder with `useMessageReminder(message.id)`, and its state with `useStateStore`:

```tsx
import { Text } from "react-native";
import type { ReminderState } from "stream-chat";
import {
  useMessageContext,
  useMessageReminder,
  useStateStore,
  WithComponents,
} from "stream-chat-react-native";

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

const ReminderLabel = () => {
  const { message } = useMessageContext();
  const reminder = useMessageReminder(message.id);
  const { timeLeftMs } =
    useStateStore(reminder?.state, reminderStateSelector) ?? {};

  if (timeLeftMs == null) return null;

  const minutes = Math.round(timeLeftMs / 60000);
  return <Text>{minutes > 0 ? `Due in ${minutes} min` : "Due now"}</Text>;
};

const SavedLabel = () => <Text>Saved</Text>;

export const App = () => (
  <WithComponents
    overrides={{
      MessageReminderHeader: ReminderLabel,
      MessageSavedForLaterHeader: SavedLabel,
    }}
  >
    {/* the rest of the app */}
  </WithComponents>
);
```

To read a message's reminder anywhere inside `Chat`, call `useMessageReminder(message.id)`. It returns the `Reminder`, or `undefined` when the message has none.

### Your own reminder list

A "Saved for later" list, following the steps in [The reminder list](#the-reminder-list):

```tsx
import { useEffect } from "react";
import { FlatList, Text } from "react-native";
import type { ReminderResponseData } from "stream-chat";
import { useChatContext, useQueryReminders } from "stream-chat-react-native";

const renderItem = ({ item }: { item: ReminderResponseData }) => (
  <Text numberOfLines={1}>
    {item.message?.user?.name}: {item.message?.text}
  </Text>
);

export const SavedForLaterList = () => {
  const { client } = useChatContext();
  const { data, loadNext } = useQueryReminders();

  useEffect(() => {
    // Setting the filter empties the list, so set it before the first query
    client.reminders.paginator.filters = { remind_at: { $exists: false } };
    void client.reminders.queryNextReminders();
  }, [client]);

  return (
    <FlatList
      data={data}
      keyExtractor={(item) => item.message_id}
      onEndReached={() => void loadNext()}
      renderItem={renderItem}
    />
  );
};
```

`useQueryReminders()` adds a reminder created afterwards to `data` whatever the filter, so a saved-for-later list also shows a new reminder with a time until it reloads. For a list of scheduled reminders, use `{ remind_at: { $exists: true } }` and sort by the deadline with `client.reminders.paginator.sort = [{ field: "remind_at", direction: 1 }]`. The SampleApp's Reminders tab (`examples/SampleApp/src/components/Reminders/RemindersList.tsx`) switches between several filters.

## Reference

- [Reminder manager](https://getstream.io/chat/docs/sdk/react-native/v10/reference/state/reminder-manager/): `client.reminders`, its state, methods and timers.
- [Reminder hooks](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/reminder-hooks/): `useMessageReminder` and `useQueryReminders`.
- [ComponentsContext](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/channel/componentscontext/): the `MessageReminderHeader` and `MessageSavedForLaterHeader` override keys.
- [Message actions](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/message-actions/): how `messageActions` works.
- [Message reminders in the JavaScript client](https://getstream.io/chat/docs/javascript/message-reminders/): the reminders API and the server setting.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/reminders-and-save-for-later/](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/reminders-and-save-for-later/).