# Reminders and save for later

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

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

The user opens the menu on a message and picks Remind me. A submenu offers times: in 2 minutes, in 30 minutes, in an hour, in 2 hours, in 8 hours and in a day. They pick 30 minutes, and the message now shows "Reminder set · Due in 30 minutes". The time counts down as it approaches. A reminder more than 59 minutes away shows its calendar time instead, such as "Today at 15:00".

Save for later marks the message "Saved for later". The same menu then offers Remove reminder or Remove save for later. A message has one reminder at most, so while it has a scheduled reminder the menu hides Save for later, and while it's saved for later the menu hides Remind me.

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.** `MessageUI` looks up the message's reminder with `useMessageReminder(message.id)` and renders `ReminderNotification` when there is one. The `remindMe` and `saveForLater` actions in the message menu create and delete reminders. `Chat` starts the manager's event subscriptions and timers when it mounts and stops them when it unmounts.
- **Layout.** 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. The Remind me submenu uses it.

### 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/v15/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 list holds what its queries returned. A reminder created or deleted afterwards changes the manager's state, but not the list until it reloads. `client.reminders.paginator` is one list per client, so screens that need different filters take turns with it.

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. The SDK doesn't act on it: the message's label keeps counting, and nothing else happens. [Alerting when a reminder is due](#alerting-when-a-reminder-is-due) adds an alert.

## 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`, and the message menu shows Remind me and Save for later only when it's `true`.

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/v15/state/state-and-entities/configuration/) explains `client.config` and how it combines with the server settings.

## Customizing

### The offered times

The Remind me submenu offers `scheduledOffsetsMs`, offsets from now in milliseconds:

```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 label

Replace `ReminderNotification` through `WithComponents`. It receives the message's `Reminder`; read its state with `useStateStore`:

```tsx
import type { Channel as ChannelType, ReminderState } from "stream-chat";
import {
  Channel,
  WithComponents,
  useStateStore,
  type ReminderNotificationProps,
} from "stream-chat-react";

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

const CustomReminderNotification = ({
  reminder,
}: ReminderNotificationProps) => {
  const { remindAt, timeLeftMs } =
    useStateStore(reminder?.state, reminderStateSelector) ?? {};

  if (!reminder) return null;
  if (remindAt == null) return <div className="reminder">🔖 Saved</div>;

  const minutes = Math.round((timeLeftMs ?? 0) / 60000);
  return (
    <div className="reminder">
      ⏰ {minutes > 0 ? `In ${minutes} min` : "Due now"}
    </div>
  );
};

export const ChannelWithCustomReminders = ({
  channel,
}: {
  channel: ChannelType;
}) => (
  <WithComponents
    overrides={{ ReminderNotification: CustomReminderNotification }}
  >
    <Channel channel={channel}>{/* MessageList, MessageComposer */}</Channel>
  </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 type { PaginatorState, ReminderResponseData } from "stream-chat";
import { useChatContext, useStateStore } from "stream-chat-react";

const reminderListSelector = ({
  hasMoreTail,
  isLoading,
  items,
}: PaginatorState<ReminderResponseData>) => ({ hasMoreTail, isLoading, items });

export const SavedForLaterList = () => {
  const { client } = useChatContext();
  const {
    hasMoreTail,
    isLoading,
    items = [],
  } = useStateStore(client.reminders.paginator.state, reminderListSelector);

  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 (
    <div className="saved-for-later">
      <ul>
        {items.map((reminder) => (
          <li key={reminder.message_id}>
            {reminder.message?.user?.name}: {reminder.message?.text}
          </li>
        ))}
      </ul>
      {hasMoreTail && (
        <button
          disabled={isLoading}
          onClick={() => void client.reminders.queryNextReminders()}
        >
          Load more
        </button>
      )}
    </div>
  );
};
```

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 }]`.

### Alerting when a reminder is due

Listen for `notification.reminder_due` and publish a notification. Render this component once, inside `Chat`:

```tsx
import { useEffect } from "react";
import { useChatContext } from "stream-chat-react";

export const ReminderDueAlerts = () => {
  const { client } = useChatContext();

  useEffect(() => {
    const { unsubscribe } = client.on("notification.reminder_due", (event) => {
      client.notifications.addInfo({
        message: `Reminder: ${event.reminder?.message?.text ?? "a saved message"}`,
        origin: { emitter: "ReminderDueAlerts" },
      });
    });
    return unsubscribe;
  }, [client]);

  return null;
};
```

[Notifications](https://getstream.io/chat/docs/sdk/react/v15/features/app-experience/notifications/) explains where notifications show.

## Reference

- [Reminder manager](https://getstream.io/chat/docs/sdk/react/v15/reference/state/reminder-manager/): `client.reminders`, its state, methods and timers.
- [Reminder hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/reminder-hooks/): `useMessageReminder`.
- [ComponentContext](https://getstream.io/chat/docs/sdk/react/v15/component-reference/channel/componentcontext/): the `ReminderNotification` override key.
- [Message actions](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/message-actions/): the Remind me and Save for later actions.
- [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/v15/features/messaging/reminders-and-save-for-later/](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/reminders-and-save-for-later/).