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;
};
Message Reminders
The Stream Chat SDK for React Native supports message reminders, allowing users to set reminders for messages they want to revisit later. This feature is particularly useful for keeping track of important messages or tasks.
Message reminders allow users to set reminders for messages. This feature is useful when users want to be notified about messages that they need to follow up on. The article explains how message reminders are managed by the React SDK.
When a reminder includes a timestamp, it’s like saying “remind me later about this message,” and the user who set it will receive a notification at the designated time. If no timestamp is provided, the reminder functions more like a bookmark, allowing the user to save the message for later reference.
Push Notifications Setup
Reminders require Push V3 to be enabled in your Stream Chat app. To do so, you can go to Push Notifications
section in the Stream Dashboard and enable Push V3 by clicking on Upgrade to V3
.
After enabling, you need to go to individual configurations, scroll down to Configure Push Notification Templates
and enable event specific notifications for notification.reminder_due
event.
Message Reminder UI Interaction
Users can create, update or delete message reminders by accessing the relevant message action from the message actions menu:
Then the message reminder notification is displayed as a part of the message UI (or removed from the UI if the reminder has been deleted):
Get Message Reminder Data
The React Native SDK components use the client.reminders
controller to perform the CRUD operations. The controller keeps reactive state - a mapping of message id to Reminder
instances. Reminder
instances have in turn their own reactive state that extends the ReminderResponse
from the server with timeLeftMs
.
- Getting the reminder for a specific message
For this, you can use the useMessageReminder
hook from the SDK, which returns a Reminder
instance for the specified message ID.
We can subscribe to the reminder state changes on two levels:
- Subscribe to a specific reminder state changes
import { useMessageReminder, useStateStore } from "stream-chat-react";
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) ?? {};
};
The timeLeftMs
value is periodically updated. The update frequency increases as the deadline is approached and decreases as it is left behind (once per day, then last day every hour, last hour every minute and vice verse when increasing the distance from the deadline).
- Subscribe to reminders pagination
The pagination is handler by the RemindersPaginator
. The SDK exposes a hook useQueryReminders
that can be used to fetch paginated reminders and handle next page, etc making sure it reacts on update/deletion of reminders.
The items paginated are not Reminder
instances but ReminderResponse
objects retrieved from the server.
import { useCallback, useEffect } from "react";
import { FlatList, StyleSheet, Text, View } from "react-native";
import {
useChatContext,
useMessageReminder,
useQueryReminders,
} from "stream-chat-react-native";
import { ReminderItem } from "./ReminderItem";
const renderItem = ({ item }: { item: ReminderResponse }) => (
<ReminderItem {...item} />
);
const renderEmptyComponent = (
<Text style={styles.emptyContainer}>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 onRefresh = useCallback(async () => {
await client.reminders.queryNextReminders();
}, [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={items}
keyExtractor={(item) => item.message.id}
renderItem={renderItem}
ListEmptyComponent={renderEmptyComponent}
ListFooterComponent={renderFooter}
onEndReached={loadNext}
/>
</View>
);
};
Message Reminder Configuration
It is possible to configure the reminder time offsets from which a user can select:
const minute = 60 * 1000;
client.reminders.updateConfig({
scheduledOffsetsMs: [30 * minute, 60 * minute],
});
Also, it is possible to configure the time boundary, when the reminder notification will stop refreshing the information about the time elapsed since the deadline:
const day = 24 * 60 * 60 * 1000;
client.reminders.updateConfig({
stopTimerRefreshBoundaryMs: day,
});
Reminder indicator on message
You can use the MessageHeader
component to customize the header of the message to add the reminder indicator on top of the message. The MessageHeader
component can be customized by passing a custom MessageHeader
component to the Channel
component:
import {
MessageFooterProps,
Time,
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 MessageReminderHeader = ({ message }: MessageFooterProps) => {
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;
}
// This is for "Saved for Later"
if (!reminder.remindAt) {
return (
<View>
<Text style={styles.headerTitle}>🔖 Saved for Later</Text>
</View>
);
}
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>
);
}
};
const styles = StyleSheet.create({
headerContainer: {
flexDirection: "row",
alignItems: "center",
},
headerTitle: {
fontSize: 14,
fontWeight: "500",
marginLeft: 4,
},
});