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

Notification hooks

These hooks read the notifications in client.notifications, publish new ones from a component, and drive a notification list. They're exported from stream-chat-react-native and stream-chat-expo.

What a notification holds, and who publishes which, is on Publishing notifications. How NotificationList shows them, and how a notification reaches a list through its target, is on Displaying notifications.

Each entry gives the signature, then:

  • Returns: what the hook gives back.
  • Reads: the store or the context it reads.
  • Re-renders: what makes the component render again.
  • Needs: where the component must sit, and what the hook does outside that place.

useNotifications

Returns the notifications in client.notifications, narrowed by the options.

useNotifications(options?: {
  filter?: (notification: Notification) => boolean;
  requireTarget?: boolean;
  target?: NotificationTarget;
}): Notification[]

The options, each optional:

  • target: { panel, hostId }. Keep only the notifications for that host, the ones a NotificationList serving it shows. A notification is for the host when it has a target tag naming the host, or a tag naming only its panel, or an origin.context.panel set to its panel. Without any of these, a composer in its origin.context that belongs to the host also makes it the host's. Routing notifications explains the tags.

  • requireTarget: return [] while target is undefined, instead of every notification.

  • filter: applied last.

  • Returns: the notifications that pass, in the order of the store. Without target, every notification in the store, system notifications included. The hook's JSDoc says it returns non-system notifications, but it doesn't filter them out: drop them with hasSystemNotificationTag in your filter. With a target, system notifications drop out, because they carry no target tag.

  • Reads: client.notifications.store, field notifications.

  • Re-renders: whenever a notification is added, removed or changed, even one the options drop. A new filter function or a new target object subscribes again, so define the filter at module scope and keep the target's identity, as in Selector identity.

  • Needs: Chat. Throws outside it.

A notification you render this way leaves only when its countdown runs, or when you remove it. Start the countdown with startNotificationTimeout(id) from useNotificationApi once it's on screen, or use useNotificationListController, which does that for you.

useSystemNotifications

Returns the system notifications: the ones about the whole app, not one part of it.

useSystemNotifications(options?: {
  filter?: (notification: Notification) => boolean;
}): Notification[]
  • Returns: the notifications tagged system, narrowed by options.filter when you pass one. No NotificationList shows them.
  • Reads: client.notifications.store, field notifications.
  • Re-renders: whenever a notification is added, removed or changed. Define filter at module scope, as for useNotifications.
  • Needs: Chat. Throws outside it.

The SDK publishes no system notification of its own: its connection indicators read the connection stores. System notifications publishes one and renders it with this hook.

useNotificationApi

Returns the functions a component publishes and removes notifications with.

useNotificationApi(): NotificationApi
  • Returns:
    • addNotification(payload, options?): publishes a notification tagged for the target the component sits in, the target of the nearest NotificationTargetProvider. Channel, Thread, ChannelList, ThreadList and the channel details screens each set one. Returns nothing.
    • addSystemNotification(payload, options?): publishes a notification tagged system and for no target. Returns its id.
    • removeNotification(id): removes a notification.
    • removeNotificationsForCurrentPanel(): removes every notification, other than system ones, for the component's target.
    • startNotificationTimeout(id, durationOverride?): starts a notification's countdown, from the beginning if it's already running. durationOverride replaces its duration. Without a duration or a durationOverride, there's no countdown.
    • runWithNotificationTarget(callback, target?): runs callback, and tags the notifications published without a target while it runs for target, by default the component's own. Returns the callback's result as a promise.
  • Reads: the target from the nearest NotificationTargetProvider. No store.
  • Re-renders: never by itself. The functions change identity when the component's target changes.
  • Needs: Chat. Throws outside it. Outside every target, addNotification publishes without a target tag, and the notification goes to the list that mounted last.

payload is what client.notifications.add() takes: { message, origin: { emitter, context? }, options? }, where options holds severity, type, duration, actions, metadata, tags and originalError. Publishing a notification describes each field.

options, the second argument of addNotification, takes:

Option What it does
target { panel, hostId }: one host to show the notification in, instead of the component's own.
targetPanels panels, such as ['channel-list'], to show it in every host of. Without target and targetPanels, a target tag already in payload.options.tags is kept as the only one.
incident { domain, entity, operation, status? }, used to build type when the payload has none. status defaults to failed for an error, success for a success, else the severity.

addSystemNotification takes only incident.

import { Pressable, Text } from "react-native";
import { useNotificationApi } from "stream-chat-react-native";

const exportIncident = { domain: "api", entity: "chat", operation: "export" };

export const ExportButton = ({
  exportChat,
}: {
  exportChat: () => Promise<void>;
}) => {
  const { addNotification } = useNotificationApi();

  const onPress = async () => {
    try {
      await exportChat();
      // Published as api:chat:export:success
      addNotification(
        {
          message: "Chat exported",
          options: { severity: "success" },
          origin: { emitter: "ExportButton" },
        },
        { incident: exportIncident },
      );
    } catch (error) {
      // Published as api:chat:export:failed
      addNotification(
        {
          message: "The chat could not be exported",
          options: {
            originalError: error instanceof Error ? error : undefined,
            severity: "error",
          },
          origin: { emitter: "ExportButton" },
        },
        { incident: exportIncident },
      );
    }
  };

  return (
    <Pressable accessibilityRole="button" onPress={onPress}>
      <Text>Export</Text>
    </Pressable>
  );
};

Outside Chat, or from code that isn't a component, publish with client.notifications directly, as Publishing a notification shows.

useNotificationListController

Does what NotificationList does, and leaves the rendering to you.

useNotificationListController(params?: {
  filter?: (notification: Notification) => boolean;
  hostId?: string;
  panel?: NotificationTargetPanel;
}): {
  dismissNotification: () => void;
  notification: Notification | null;
}

The target it serves is { panel, hostId } when you pass both. With neither, it's the target of the nearest NotificationTargetProvider. With only panel, it's that provider's target when its panel matches, otherwise none.

  • Returns: notification, the one to show, or null. dismissNotification() removes it.
  • Reads: client.notifications.store, through useNotifications({ target, filter, requireTarget: true }). System notifications are left out.
  • Re-renders: whenever a notification is added, removed or changed.
  • Needs: Chat. Throws outside it. Without a target it returns null.

While it's mounted it does the list's work:

  1. It picks the notification to show. A notification without a duration goes first; otherwise the newest one shows.
  2. It removes every other notification for its target from the store.
  3. It starts the countdown of the notification it shows, at least 5 seconds for one with actions.
  4. It registers its target as the latest mounted one, so a notification published without a target goes to it while it's the latest.
  5. When it unmounts, it removes every notification for its target.
import { Pressable, Text, View } from "react-native";
import { useNotificationListController } from "stream-chat-react-native";

export const TopBanner = () => {
  const { dismissNotification, notification } = useNotificationListController();

  if (!notification) return null;

  return (
    <View accessibilityLiveRegion="polite">
      <Text>{notification.message}</Text>
      <Pressable accessibilityRole="button" onPress={dismissNotification}>
        <Text>Close</Text>
      </Pressable>
    </View>
  );
};

Render it inside a target, such as a Channel. Next to the default NotificationList of the same target, both pick the same notification. Replace the list through WithComponents instead, as Replacing the list or a notification shows.

useNotificationTarget

Returns the kind of surface the component sits in.

useNotificationTarget(): NotificationTargetPanel | undefined
  • Returns, the first that applies:
    1. 'thread' inside a Thread;
    2. 'channel' inside a Channel;
    3. 'thread-list' inside a ThreadList;
    4. 'channel-list' inside a ChannelList;
    5. undefined.
  • Reads: the Thread, Channel, ThreadList and ChannelList contexts. No store.
  • Re-renders: when one of those contexts changes.
  • Needs: nothing. Never throws.

It returns only the panel, never a host ID, and it doesn't know about channel details screens. For the exact target, use useNotificationTargetContext().

useNotificationTargetContext

Returns the target of the nearest NotificationTargetProvider.

useNotificationTargetContext(): NotificationTarget | undefined
  • Returns: { panel, hostId }, or undefined outside every provider.
  • Reads: the context of NotificationTargetProvider. No store.
  • Re-renders: when the provider's panel or hostId changes.
  • Needs: nothing. Never throws.

useResolvedNotificationTarget({ hostId?, panel? }) resolves a target the way useNotificationListController does, from the params or this context.

To give a part of your app a list of its own, wrap it in a provider and render a list inside it:

import type { PropsWithChildren } from "react";
import { View } from "react-native";
import {
  NotificationList,
  NotificationTargetProvider,
} from "stream-chat-react-native";

export const SettingsArea = ({ children }: PropsWithChildren) => (
  <NotificationTargetProvider hostId="settings" panel="settings">
    <View style={{ flex: 1 }}>
      {children}
      <NotificationList />
    </View>
  </NotificationTargetProvider>
);

useClientNotifications

Returns every notification in the store.

useClientNotifications(): { notifications: Notification[] }
  • Returns: { notifications }, the whole list, system notifications included.
  • Reads: client.notifications.store, field notifications.
  • Re-renders: when the list changes.
  • Needs: Chat. Throws outside it.

useNotifications() without options returns the same list.