stream-chat-react-native-core
Version:
The official React Native and Expo components for Stream Chat, a service for building chat applications
161 lines (129 loc) • 5.35 kB
text/typescript
import { useEffect } from 'react';
import type { Notification as NotificationType } from 'stream-chat';
import { hasSystemNotificationTag, useNotificationApi } from './useNotificationApi';
import { useNotifications, type UseNotificationsFilter } from './useNotifications';
import { useChatContext } from '../../../contexts/chatContext/ChatContext';
import { useLazyRef } from '../../../hooks/useLazyRef';
import { useStableCallback } from '../../../hooks/useStableCallback';
import {
isNotificationForTarget,
registerActiveNotificationTarget,
resolveNotificationTargetTagIfNeeded,
type NotificationTargetPanel,
} from '../notificationTarget';
import { useResolvedNotificationTarget } from '../NotificationTargetContext';
export type UseNotificationListControllerParams = {
filter?: UseNotificationsFilter;
hostId?: string;
panel?: NotificationTargetPanel;
};
export type UseNotificationListControllerResult = {
dismissNotification: () => void;
notification: NotificationType | null;
};
const ACTION_NOTIFICATION_DURATION = 5000;
const getNewestNotification = (notifications: NotificationType[]) =>
notifications.reduce<NotificationType | null>(
(newest, notification) =>
!newest || notification.createdAt >= newest.createdAt ? notification : newest,
null,
);
const isPersistentNotification = (notification: NotificationType) => !notification.duration;
const getActiveNotification = (notifications: NotificationType[]) => {
const persistentNotifications = notifications.filter(isPersistentNotification);
return getNewestNotification(
persistentNotifications.length > 0 ? persistentNotifications : notifications,
);
};
const getNotificationDurationOverride = (notification: NotificationType) => {
if (isPersistentNotification(notification)) return undefined;
if (!notification.actions?.length) return undefined;
return Math.max(notification.duration ?? 0, ACTION_NOTIFICATION_DURATION);
};
export const useNotificationListController = ({
filter,
hostId,
panel,
}: UseNotificationListControllerParams = {}): UseNotificationListControllerResult => {
const { client } = useChatContext();
const { removeNotification, startNotificationTimeout } = useNotificationApi();
const target = useResolvedNotificationTarget({ hostId, panel });
const startedTimeoutIdsRef = useLazyRef<Set<string>>(() => new Set());
const combinedFilter = useStableCallback((notification: NotificationType) => {
if (hasSystemNotificationTag(notification)) return false;
return filter ? filter(notification) : true;
});
const notifications = useNotifications({
filter: combinedFilter,
requireTarget: true,
target,
});
const notification = getActiveNotification(notifications);
const dismissNotification = useStableCallback(() => {
if (!notification) return;
startedTimeoutIdsRef.current.delete(notification.id);
removeNotification(notification.id);
});
const removeCurrentPanelNotifications = useStableCallback(() => {
if (!target) return;
startedTimeoutIdsRef.current.clear();
client.notifications.notifications
.filter(
(notification) =>
!hasSystemNotificationTag(notification) && isNotificationForTarget(notification, target),
)
.forEach(({ id }) => removeNotification(id));
});
useEffect(() => {
return client.notifications.store.addPreprocessor((nextState, previousState) => {
const previousNotificationIds = new Set(
previousState?.notifications.map(({ id }) => id) ?? [],
);
nextState.notifications.forEach((notification) => {
if (previousNotificationIds.has(notification.id)) return;
if (hasSystemNotificationTag(notification)) return;
resolveNotificationTargetTagIfNeeded(client.notifications, notification);
});
});
}, [client.notifications]);
useEffect(() => {
if (!target) return;
return registerActiveNotificationTarget(client.notifications, target);
}, [client.notifications, target]);
useEffect(() => {
const notificationIds = new Set(notifications.map(({ id }) => id));
startedTimeoutIdsRef.current.forEach((id) => {
if (!notificationIds.has(id)) {
startedTimeoutIdsRef.current.delete(id);
}
});
}, [notifications, startedTimeoutIdsRef]);
useEffect(() => {
if (!notification || notifications.length <= 1) return;
notifications.forEach(({ id }) => {
if (id === notification.id) return;
startedTimeoutIdsRef.current.delete(id);
removeNotification(id);
});
}, [notification, notifications, removeNotification, startedTimeoutIdsRef]);
useEffect(() => {
if (!notification) return;
if (startedTimeoutIdsRef.current.has(notification.id)) return;
startedTimeoutIdsRef.current.add(notification.id);
const durationOverride = getNotificationDurationOverride(notification);
if (typeof durationOverride === 'number') {
startNotificationTimeout(notification.id, durationOverride);
} else {
startNotificationTimeout(notification.id);
}
}, [notification, startNotificationTimeout, startedTimeoutIdsRef]);
useEffect(() => {
return () => {
removeCurrentPanelNotifications();
};
}, [removeCurrentPanelNotifications]);
return {
dismissNotification,
notification,
};
};