const { openInAppNotification, closeInAppNotification, notifications } =
useInAppNotificationsState();In-App Notifications
The SDK provides in-app notifications via a built-in Toaster or your own implementation.
Best Practices
- Render the toaster near the app root to avoid clipping and z-index issues.
- Use
useInAppNotificationsStateas the single source of truth for UI. - Keep notifications brief and actionable; avoid stacking too many at once.
- Set clear severity styles for quick scanning.
- Close notifications on navigation changes to prevent stale context.
In-app notifications
In this cookbook, we will show you how to use the built-in toaster to display in-app notifications.
Store
The SDK provides a store for in-app notifications, exposed via useInAppNotificationsState.
The hook returns:
openInAppNotification: Opens a new notification. This can be used to display a notification for a specific action.closeInAppNotification: Closes a notification. Calling this method will remove the notification from the store.notifications: A list of all the notifications. This is a read-only list of the notifications that are currently displayed.
Use this hook to drive your Toaster UI.
Creating a Custom Toaster
Build a custom toaster with useInAppNotificationsState.
An example of a custom toaster is the following:
import {
Dimensions,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
import Animated, {
Easing,
SlideInDown,
SlideOutDown,
} from "react-native-reanimated";
import {
SafeAreaView,
useSafeAreaInsets,
} from "react-native-safe-area-context";
import { useInAppNotificationsState, useTheme } from "stream-chat-react-native";
import type { Notification } from "stream-chat";
const { width } = Dimensions.get("window");
const severityIconMap: Record<Notification["severity"], string> = {
error: "❌",
success: "✅",
warning: "⚠️",
info: "ℹ️",
};
export const Toast = () => {
const { closeInAppNotification, notifications } =
useInAppNotificationsState();
const { top } = useSafeAreaInsets();
const {
theme: {
colors: { overlay, white_smoke },
},
} = useTheme();
return (
<SafeAreaView style={[styles.container, { top }]} pointerEvents="box-none">
{notifications.map((notification) => (
<Animated.View
key={notification.id}
entering={SlideInDown.easing(Easing.bezierFn(0.25, 0.1, 0.25, 1.0))}
exiting={SlideOutDown}
style={[styles.toast, { backgroundColor: overlay }]}
>
<View style={[styles.icon, { backgroundColor: overlay }]}>
<Text style={[styles.iconText, { color: white_smoke }]}>
{severityIconMap[notification.severity]}
</Text>
</View>
<View style={styles.content}>
<Text style={[styles.message, { color: white_smoke }]}>
{notification.message}
</Text>
</View>
<TouchableOpacity
onPress={() => closeInAppNotification(notification.id)}
>
<Text style={[styles.close, { color: white_smoke }]}>✕</Text>
</TouchableOpacity>
</Animated.View>
))}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
position: "absolute",
right: 16,
left: 16,
alignItems: "flex-end",
},
toast: {
width: width * 0.9,
borderRadius: 12,
padding: 12,
marginBottom: 8,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
shadowColor: "#000",
shadowOpacity: 0.2,
shadowRadius: 4,
elevation: 5,
},
content: {
flex: 1,
marginHorizontal: 8,
},
message: {
fontSize: 14,
fontWeight: "600",
},
close: {
fontSize: 16,
},
icon: {
width: 20,
height: 20,
borderRadius: 12,
justifyContent: "center",
alignItems: "center",
},
iconText: {
fontWeight: "bold",
includeFontPadding: false,
},
warning: {
backgroundColor: "yellow",
},
});This example uses the Notification type from stream-chat.
Triggering a notification
You can trigger the notification by calling the openInAppNotification method with the notification object.
openInAppNotification({
id: "1",
message: "This is a test notification",
severity: "info",
created_at: Date.now(),
origin: {
emitter: "test_emitter",
id: "test_id",
},
});Configuring the Toaster
Render Toast near the top of your app hierarchy.
<Toast />Closing a Toaster
The toaster will automatically close after a certain amount of time. You can also close the toaster manually by calling the closeInAppNotification method.
Client-side notifications
To listen to client side notifications, you can use the useClientNotifications hook exposed from the SDK.
const { notifications } = useClientNotifications();This hook returns the following methods:
notifications: A list of all the notifications. This is a read-only list of the notifications that are currently displayed.
Compare the notifications list to decide when to show a toast.
In the Sample App, useClientNotificationsHandler listens to client-side notifications and triggers the toaster.
This is how it looks like:
import type { Notification } from "stream-chat";
import {
useClientNotifications,
useInAppNotificationsState,
} from "stream-chat-react-native";
import { useEffect, useMemo, useRef } from "react";
export const usePreviousNotifications = (notifications: Notification[]) => {
const prevNotifications = useRef<Notification[]>(notifications);
const difference = useMemo(() => {
const prevIds = new Set(
prevNotifications.current.map((notification) => notification.id),
);
const newIds = new Set(
notifications.map((notification) => notification.id),
);
return {
added: notifications.filter(
(notification) => !prevIds.has(notification.id),
),
removed: prevNotifications.current.filter(
(notification) => !newIds.has(notification.id),
),
};
}, [notifications]);
prevNotifications.current = notifications;
return difference;
};
/**
* This hook is used to open and close the notifications when the notifications are added or removed.
* @returns {void}
*/
export const useClientNotificationsHandler = () => {
const { notifications } = useClientNotifications();
const { openInAppNotification, closeInAppNotification } =
useInAppNotificationsState();
const { added, removed } = usePreviousNotifications(notifications);
useEffect(() => {
added.forEach(openInAppNotification);
removed.forEach((notification) => closeInAppNotification(notification.id));
}, [added, closeInAppNotification, openInAppNotification, removed]);
};