Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

Reminder manager

client.reminders is the reminder manager, a ReminderManager from stream-chat. It keeps one Reminder per message, talks to the reminders API, and holds the user's reminder list. A reminder with a remind_at time is "remind me"; one without is "save for later". This page lists its API.

How reminders work end to end, with a "Saved for later" list and an alert when a reminder is due, is on Reminders and save for later.

Getting the manager

The client creates the manager when it's constructed, so there is one per client: client.reminders. Don't construct a ReminderManager yourself.

The manager follows events only while its subscriptions run:

Method What it does
registerSubscriptions() Starts applying the reminder and message events listed in Events. A second call does nothing.
unregisterSubscriptions() Stops applying them.
initTimers() Starts the timer of every reminder in the state.
clearTimers() Stops the timer of every reminder in the state.

The UI layer starts the subscriptions and the timers for you, and stops them when it's done. Code that uses the client without a UI layer calls these itself.

State

The manager's state

client.reminders.state is a store with one field:

Field Type What it holds
reminders Map<string, Reminder> One Reminder per message, keyed by the message id. A new Map on every change.

The map changes when a reminder is added or removed. A change to an existing reminder, such as a new remind_at, is published on that reminder's own state, not here. So following one message's reminder takes two subscriptions: one to find the Reminder, one to follow it.

const messageId = "message-1";

const unsubscribe = client.reminders.state.subscribeWithSelector(
  ({ reminders }) => ({ reminder: reminders.get(messageId) }),
  ({ reminder }) => {
    const remindAt = reminder?.remindAt;
    console.log(
      remindAt != null
        ? `Due ${nsToDate(remindAt).toLocaleString()}`
        : "No time set",
    );
  },
);

The getters of client.reminders return the value at the moment you read them, and don't tell you when it changes:

Getter or method Returns
reminders The current Map of reminders.
getFromState(messageId) The message's Reminder, or undefined.

Each reminder's state

A Reminder has its own state store, of type ReminderState. The timestamps are unix nanoseconds, as the API sends them: convert them with nsToDate() or nsToMs(), as Data conventions explains.

Field Type What it holds
message_id string The message the reminder belongs to. It's also the reminder's id.
channel_cid string The channel of the message.
remind_at TimestampNS or null When the reminder is due. null for a message saved for later.
timeLeftMs number or null Milliseconds until remind_at, negative once it's due. null without a remind_at. The timer refreshes it.
message MessageResponse or null The message, when the server sent it with the reminder.
user_id string The user the reminder belongs to.
user UserResponse or null That user, when the server sent it.
created_at TimestampNS When the reminder was created.
updated_at TimestampNS When it last changed.

A Reminder also has:

Member What it does
id Getter: the message id.
remindAt Getter: remind_at.
timeLeftMs Getter: timeLeftMs.
timer The reminder's ReminderTimer. See Timers.
setState(data: ReminderResponseData) Replaces the state with server data, then starts the timer, or stops it when there is no remind_at.
refreshTimeLeft() Recomputes timeLeftMs from the clock.
initTimer(), clearTimer() Starts or stops the reminder's timer.

timeLeftMs(remindAt), exported from stream-chat, computes the same value as the state field for any nanosecond timestamp.

The reminder list

client.reminders.paginator is a ReminderPaginator: the current user's reminders across all channels, loaded page by page. It's one paginator per client. Its state has the fields every paginator in the state layer has (items, isLoading, hasMoreHead, hasMoreTail, lastQueryError), and How pagination works explains them. The base paginator API is on Channel manager and ChannelPaginator.

Member What it does
items Reminder records from the server (ReminderResponseData), each with its message and channel. They aren't Reminder instances.
filters Getter and setter. Filters on channel_cid, message_id, created_at, remind_at and expires_at. { remind_at: { $exists: false } } keeps the saved-for-later ones. Setting it empties the list.
sort Getter and setter, such as [{ field: "remind_at", direction: 1 }]. Setting it empties the list. Unset, the list is ordered by created_at.
pageSize Reminders per page, 10 by default.
reload() Empties the list and loads the first page.

While the manager's subscriptions run, every page the list loads also goes into the manager's state, so each loaded reminder gets its Reminder. The list itself holds what its queries returned: a reminder created or deleted later changes the manager's state, but not the list until it reloads.

Methods

None of these is optimistic. The manager's state changes once the server answers.

Method Returns What it does
createReminder({ message_id }, { remind_at?, expires_at? }?) Promise<Reminder | undefined> Creates the message's reminder and adds it to the state. Without remind_at, the message is saved for later.
updateReminder({ message_id }, { remind_at?, expires_at? }?) Promise<Reminder | undefined> Changes the message's reminder and updates the state.
upsertReminder({ message_id }, { remind_at?, expires_at? }?) Promise<Reminder | undefined> Updates the reminder when the state has one for the message, and creates it otherwise. When the server answers that the reminder already exists, or that it doesn't, it makes the other call.
deleteReminder(messageId: string) Promise<void> Deletes the message's reminder, then removes it from the state and stops its timer.
queryNextReminders() Promise<void> Loads the next page of paginator, or the first page when nothing is loaded.
queryPreviousReminders() Promise<void> Loads a page at the other end of paginator.

remind_at and expires_at are Date objects in a request, and the methods take an optional third argument with request options. They resolve to the Reminder that was already in the state. For a message that had no reminder yet, they add a new Reminder and resolve to undefined: read it with ReminderManager.getFromState(messageId) afterwards.

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

const reminder = client.reminders.getFromState(message.id);

These methods write the state without a request. The manager uses them itself, and they don't change anything on the server:

Method What it does
upsertToState({ data, overwrite? }) Adds a Reminder for data.message_id, or, with overwrite (true by default), updates the existing one.
removeFromState(messageId) Removes the message's reminder and stops its timer.
hydrateState(messages) Adds the reminder of each message that carries one.

Configuration

The manager has the config, configState and ReminderManager.updateConfig() that every configurable object has, and ReminderManager.initializeConfig(), which the client calls to rebuild the settings from client.config. In client.config, the settings are the reminders group of the client key:

Setting Default What it does
scheduledOffsetsMs 2 minutes, 30 minutes, 1, 2 and 8 hours, 1 day The "remind me in…" choices a UI offers, as offsets from now in milliseconds.
stopTimerRefreshBoundaryMs 2 weeks How long after its deadline a reminder's timer keeps refreshing timeLeftMs.

The getters scheduledOffsetsMs and stopTimerRefreshBoundaryMs read the current values. A change to stopTimerRefreshBoundaryMs reaches the timers that are running. Configuration tree lists both, and Configuration explains how to set them.

Whether reminders are offered in a channel is a channel setting: channel.config.userMessageReminders.enabled, which the channel type's user_message_reminders can turn off.

Events

While its subscriptions run, the manager applies these events:

Event What changes
reminder.created The reminder is added, or updated if it's in the state already.
reminder.updated The reminder is updated, or added.
reminder.deleted The reminder is removed.
message.deleted The message's reminder is removed.
message.undeleted The message's reminder is added back, when the message carries one.
notification.reminder_due Nothing. The manager doesn't act on it.

ReminderManager.isReminderWsEventPayload(event) tells whether an event is a reminder.* or notification.reminder_due event that carries a reminder.

Reminders also arrive with messages. A channel query and client.queryChannels() add the reminder of every message they load, with or without subscriptions.

Timers

Each Reminder with a remind_at has a timer that refreshes timeLeftMs in its state. The timer starts when the reminder is created or gets a remind_at, and stops when the reminder is removed or loses it. It refreshes more often the closer the deadline is, before and after it:

Time to or from the deadline The timer fires
More than a day Once a day.
Between an hour and a day Once an hour.
Between a minute and an hour Once a minute.
Less than a minute Once, when that time has passed. Before the deadline, that's the deadline itself.

Once a reminder is overdue by stopTimerRefreshBoundaryMs, two weeks by default, its timer stops and timeLeftMs stays as it was last set.

A tick changes only that reminder's timeLeftMs. The manager's reminders map doesn't change, and nothing is published elsewhere. When the reminder comes due, the server sends notification.reminder_due, which the manager ignores. To alert the user, listen for that event, as Alerting when a reminder is due shows.

reminder.timer is a ReminderTimer:

Member What it is
stopRefreshBoundaryMs The boundary this timer uses. The manager sets it from its configuration.
timeout The pending timeout, or null when the timer isn't running.
getRefreshIntervalLength() The delay until the next tick, from the table above, or null without a remind_at.
init() Schedules the next tick. Does nothing once the boundary has passed.
clear() Cancels the pending tick.

See also