# In-App Notifications

The SDK emits in-app notifications for built-in actions (delete a message, vote on a poll, upload an attachment, and so on) and renders them as snackbars via [`NotificationList`](https://getstream.io/chat/docs/sdk/react-native/ui-components/in-app-notifications/notification-list/). You can also emit notifications from your own components with [`useNotificationApi`](https://getstream.io/chat/docs/sdk/react-native/ui-components/in-app-notifications/hooks/use-notification-api/), and read the notification stream with [`useNotifications`](https://getstream.io/chat/docs/sdk/react-native/ui-components/in-app-notifications/hooks/use-notifications/).

This cookbook covers emitting and reading. For visual customization (theme, overrides, custom snackbar), see the [Snackbar cookbook](https://getstream.io/chat/docs/sdk/react-native/guides/snackbar/). For concepts (severity, panels, lifecycle), see the [In-App Notifications guide](https://getstream.io/chat/docs/sdk/react-native/guides/in-app-notifications-overview/).

## Best Practices

- Mount `NotificationList` inside each panel you want to surface notifications in (`MessageList`, `ChannelList`, `ThreadList`), not at the app root, so the snackbar appears in context.
- Always set an `origin.emitter` that identifies the component or feature emitting the notification. Consumers (analytics, tests, custom filters) rely on it.
- Use `incident` instead of writing the `type` string by hand. The SDK builds `domain:entity:operation:status` from the incident so types stay consistent.
- Keep messages short: one line, ideally under 60 characters.
- Use `addSystemNotification` for anything that shouldn't appear in the snackbar (analytics, dev warnings, background errors).

## Mounting `NotificationList`

`MessageList`, `ChannelList`, and `ThreadList` mount a `NotificationList` for you by default. You don't need to mount one yourself to see the SDK's built-in notifications. If you've replaced one of those components or are building a fully custom layout, mount the snackbar host explicitly:

```tsx
import {
  Channel,
  MessageList,
  MessageInput,
  NotificationList,
} from "stream-chat-react-native";

export const ChannelScreen = () => (
  <Channel channel={channel}>
    <MessageList />
    <MessageInput />
    <NotificationList panel="channel" verticalAlignment="bottom" />
  </Channel>
);
```

When mounted inside the SDK's notification target context, the `panel` prop is optional because `NotificationList` resolves the target from the surrounding provider.

## Emitting a Notification

`useNotificationApi` returns the emit/remove helpers. Pass an `AddNotificationPayload` (which always contains `message` and `origin.emitter`) and an optional second argument with extras like `incident` or `targetPanels`.

```tsx
import { useNotificationApi } from "stream-chat-react-native";

export const SaveDraftButton = () => {
  const { addNotification } = useNotificationApi();

  const handlePress = async () => {
    try {
      await saveDraft();
      addNotification({
        message: "Draft saved",
        origin: { emitter: "SaveDraftButton" },
        options: { severity: "success" },
      });
    } catch (error) {
      addNotification(
        {
          message: "Failed to save draft",
          origin: { emitter: "SaveDraftButton" },
          options: {
            severity: "error",
            originalError: error instanceof Error ? error : undefined,
          },
        },
        {
          incident: { domain: "api", entity: "draft", operation: "save" },
        },
      );
    }
  };

  return <Button onPress={handlePress} title="Save draft" />;
};
```

The SDK auto-routes this notification to whichever panel the component is mounted in (channel, thread, channel list, or thread list).

## Adding Action Buttons

A notification can carry up to a few action buttons. Each action is `{ label, handler }`. When at least one action is present, the dismiss timer is automatically extended to a minimum of 5 seconds so the user has time to tap.

```tsx
const { addNotification } = useNotificationApi();

addNotification({
  message: "Message deleted",
  origin: { emitter: "MessageActions" },
  options: {
    severity: "success",
    actions: [
      {
        label: "Undo",
        handler: () => restoreMessage(message),
      },
    ],
  },
});
```

## Persistent vs Transient

By default, a notification auto-dismisses after the severity's configured duration (3 seconds for `error`, `warning`, `success`, and `info`). Set `duration: 0` to keep it visible until the user, or your code, dismisses it. Notifications without a resolved duration are persistent too. The snackbar shows a close button on persistent notifications automatically.

```tsx
addNotification({
  message: "Tap to retry the connection",
  origin: { emitter: "ConnectionBanner" },
  options: {
    duration: 0,
    severity: "warning",
  },
});
```

Persistent notifications take priority over transient ones. If both are present, the snackbar shows the persistent one.

## Generating Types from Incidents

The `type` field categorizes a notification for analytics, dedupe, and translation. Instead of writing the string by hand, describe the incident and the SDK builds the type for you:

```tsx
addNotification(
  {
    message: "Failed to send message",
    origin: { emitter: "MessageComposer" },
    options: { severity: "error" },
  },
  {
    incident: { domain: "api", entity: "message", operation: "send" },
    // → type: 'api:message:send:failed'   (status inferred from severity='error')
  },
);
```

`status` defaults to `'failed'` for error severity, `'success'` for success severity, otherwise the severity value itself. Pass `incident.status` explicitly to override.

## Targeting a Specific Panel

By default, the SDK uses the nearest `NotificationTargetProvider` to resolve the target. The built-in `Channel`, `Thread`, `ChannelList`, and `ThreadList` components install that provider for you. Override with `targetPanels` to fan out a notification to specific panels regardless of where the call comes from:

```tsx
addNotification(
  {
    message: "Channel pinned",
    origin: { emitter: "ChannelListItem" },
    options: { severity: "success" },
  },
  {
    targetPanels: ["channel-list"],
  },
);
```

Pass multiple panels to broadcast a notification to several at once.

## Routing Notifications from a Background Action

When a network call starts inside a channel but completes after the user has navigated away, for example, the user pins a channel from the channel list, you want the resulting notification to land back in the original panel, not the panel that's active when the call resolves.

`runWithNotificationTarget` registers an "action target" for the duration of the callback. Any notification emitted during the callback is routed to that target, even if the user has moved on:

```tsx
import {
  getChannelNotificationHostId,
  useNotificationApi,
} from "stream-chat-react-native";
import type { Channel as StreamChannel } from "stream-chat";

const { addNotification, runWithNotificationTarget } = useNotificationApi();

const handlePin = async (channel: StreamChannel) => {
  await runWithNotificationTarget(
    async () => {
      try {
        await channel.pin();
        addNotification({
          message: "Channel pinned",
          origin: { emitter: "ChannelListItem" },
          options: { severity: "success" },
        });
      } catch (error) {
        addNotification({
          message: "Failed to pin channel",
          origin: { emitter: "ChannelListItem" },
          options: { severity: "error" },
        });
      }
    },
    { hostId: getChannelNotificationHostId(channel.cid), panel: "channel" },
  );
};
```

The SDK uses this pattern internally for message actions, channel actions, and poll mutations.

## Reading Notifications

To react to notifications elsewhere in your app, for example, to forward errors to your analytics pipeline, call `useNotifications`:

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

export const useAnalyticsLogger = () => {
  const notifications = useNotifications({
    filter: (n) => n.severity === "error",
  });

  useEffect(() => {
    const last = notifications[notifications.length - 1];
    if (!last) return;

    analytics.track("chat_error", {
      emitter: last.origin.emitter,
      type: last.type,
      message: last.message,
    });
  }, [notifications]);
};
```

Without options, `useNotifications` returns every notification in the store. Pass an explicit `target` to read notifications for a specific channel, thread, channel list, or thread list.

## System Notifications

Some notifications shouldn't appear in the snackbar, such as analytics, internal warnings, or background sync errors. Emit them with `addSystemNotification`:

```tsx
const { addSystemNotification } = useNotificationApi();

addSystemNotification({
  message: "Background sync failed",
  origin: { emitter: "OfflineSync" },
  options: { severity: "warning" },
});
```

Read them with [`useSystemNotifications`](https://getstream.io/chat/docs/sdk/react-native/ui-components/in-app-notifications/hooks/use-system-notifications/). They never reach `NotificationList`.

## Dismissing Programmatically

Call `removeNotification(id)` to dismiss a specific notification. `addSystemNotification` returns its id; `addNotification` does not, so capture the id from the notification store if you need to dismiss a snackbar notification.

```tsx
const { addSystemNotification, removeNotification } = useNotificationApi();

const id = addSystemNotification({
  message: "Reconnecting…",
  origin: { emitter: "OfflineSync" },
  options: { severity: "loading", duration: 0 },
});

await reconnect();
removeNotification(id);
```

To clear every notification in the current target provider, useful when the user navigates away, call `removeNotificationsForCurrentPanel`. `NotificationList` already does this when it unmounts, so you only need it for custom layouts.

## Migration from `useInAppNotificationsState`

If you previously rolled your own toaster on top of `useInAppNotificationsState`, the migration is small but mechanical:

| Old                                                   | New                                                                                     |
| ----------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `openInAppNotification({ id, message, severity, … })` | `addNotification({ message, origin, options: { severity, … } })`                        |
| `closeInAppNotification(id)`                          | `removeNotification(id)`                                                                |
| Custom `<Toast />` at app root                        | `<NotificationList />` inside each panel, or remove yours when using the built-in hosts |
| `useClientNotifications().notifications`              | `useNotifications()`                                                                    |

You can delete your custom toaster component in the common case. `MessageList`, `ChannelList`, and `ThreadList` mount `NotificationList` for you, while `Channel` and `Thread` provide the target context around their message lists.


---

This page was last updated at 2026-08-17T13:13:20.642Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/guides/in-app-notifications/](https://getstream.io/chat/docs/sdk/react-native/guides/in-app-notifications/).