docs/platform/sdks/react/hooks/use-notifications.mdx
The useNotifications hook provides a way to fetch and manage notifications in your application. It includes support for pagination, filtering, and real-time updates.
| Property | Type | Description |
|---|---|---|
tags | TagsFilter | |
data | Record<string, unknown> | |
read | boolean | |
archived | boolean | |
snoozed | boolean | |
seen | boolean | |
severity | SeverityLevelEnum | SeverityLevelEnum[] | |
createdGte | number | |
createdLte | number | |
limit | number | |
realtime | boolean | When false, disables the WebSocket subscription that auto-injects newly received notifications into the list. Built-in mutations like readAll, seenAll, archiveAll, and archiveAllRead continue to update local state. Use this when you want to drive new-notification updates yourself (e.g. via your own novu.on('notifications.notification_received', ...) handler combined with refetch()). When set, this prop takes precedence over the realtime config on <NovuProvider />. When omitted, the provider value (default true) is used. |
onSuccess | (data: Notification[]) => void | |
onError | (error: NovuError) => void |
| Property | Type | Description |
|---|---|---|
notifications | Notification[] | |
error | NovuError | |
isLoading | boolean | |
isFetching | boolean | |
hasMore | boolean | |
readAll | () => Promise<{ data?: void | undefined; error?: NovuError | undefined; }> | |
seenAll | () => Promise<{ data?: void | undefined; error?: NovuError | undefined; }> | |
archiveAll | () => Promise<{ data?: void | undefined; error?: NovuError | undefined; }> | |
archiveAllRead | () => Promise<{ data?: void | undefined; error?: NovuError | undefined; }> | |
refetch | () => Promise<void> | |
fetchMore | () => Promise<void> |
The Notification type from @novu/react includes many properties. Here are the most commonly used ones:
| Property | Type | Description |
|---|---|---|
id | string | |
transactionId | string | |
subject | string | |
body | string | |
to | Subscriber | |
isRead | boolean | |
isSeen | boolean | |
isArchived | boolean | |
isSnoozed | boolean | |
snoozedUntil | string | |
deliveredAt | string[] | |
createdAt | string | |
readAt | string | |
firstSeenAt | string | |
archivedAt | string | |
avatar | string | |
primaryAction | Action | |
secondaryAction | Action | |
channelType | ChannelType | |
tags | string[] | |
redirect | Redirect | |
data | NotificationData | |
workflow | Workflow | |
severity | SeverityLevelEnum | |
read | () => Result<Notification> | |
unread | () => Result<Notification> | |
seen | () => Result<Notification> | |
archive | () => Result<Notification> | |
unarchive | () => Result<Notification> | |
delete | () => Result<void> | |
snooze | (snoozeUntil: string) => Result<Notification> | |
unsnooze | () => Result<Notification> | |
completePrimary | () => Result<Notification> | |
completeSecondary | () => Result<Notification> | |
revertPrimary | () => Result<Notification> | |
revertSecondary | () => Result<Notification> | |
on | <Key extends EventNames>(eventName: Key, listener: EventHandler<Events[Key]>) => () => void | |
off | <Key extends EventNames>(eventName: Key, listener: EventHandler<Events[Key]>) => void |
Here's how to use the useNotifications hook to fetch and display notifications:
import { useNotifications } from "@novu/react";
function NotificationsList() {
const { notifications, hasMore, isLoading, error, fetchMore } =
useNotifications();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div className="space-y-4">
{notifications?.map((notification) => (
<div key={notification.id} className="p-4 border rounded-lg">
<h3 className="font-medium">{notification.subject}</h3>
<p>{notification.body}</p>
<div className="flex justify-between text-sm text-gray-500 mt-2">
<span>{new Date(notification.createdAt).toLocaleString()}</span>
<span>{notification.isRead ? "Read" : "Unread"}</span>
</div>
</div>
))}
{hasMore && (
<button
onClick={fetchMore}
className="w-full p-2 bg-blue-50 text-blue-600 rounded-md"
>
Load More
</button>
)}
</div>
);
}
You can filter notifications by various properties:
read - Filter notifications by read statusseen - Filter notifications by seen statustags - Filter notifications by tags. If multiple tags are provided, Novu evaluates them using OR logic, meaning a notification is included if it contains at least one of the specified tags.data - Filter notifications by data attributes (key-value pairs).severity - Filter notifications by severitylimit - Fetch notifications per pagecreatedGte - Fetch notifications created after the given date (in milliseconds)createdLte - Fetch notifications created before the given date (in milliseconds)import { useNotifications } from "@novu/react";
import dayjs from "dayjs";
const now = dayjs();
// convert to milliseconds
const startDate = now.subtract(60, "days").valueOf();
const endDate = now.valueOf();
function FilteredNotifications() {
const { notifications, isLoading } = useNotifications({
read: false, // Only unread notifications
seen: false, // Only unseen notifications
tags: ["important", "urgent"], // Only notifications with these tags (OR logic applied)
// data attributes
data: {
type: "login",
userName: "John Doe",
},
severity: SeverityLevelEnum.HIGH, // Only high severity notifications
limit: 20, // Fetch 20 notifications per page
createdGte: startDate, // Fetch notifications created in the last 60 days
createdLte: endDate,
});
if (isLoading) return <div>Loading...</div>;
return (
<div className="space-y-4">
{notifications?.map((notification) => (
<div key={notification.id} className="p-4 border rounded-lg">
<h3 className="font-medium">{notification.subject}</h3>
<p>{notification.body}</p>
</div>
))}
</div>
);
}
You can implement infinite scroll using the fetchMore function:
import { useEffect, useRef } from "react";
import { useNotifications } from "@novu/react";
function InfiniteNotificationsList() {
const { notifications, hasMore, isLoading, fetchMore } = useNotifications();
const observerTarget = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasMore && !isLoading) {
fetchMore();
}
},
{ threshold: 0.5 },
);
if (observerTarget.current) {
observer.observe(observerTarget.current);
}
return () => observer.disconnect();
}, [hasMore, isLoading, fetchMore]);
if (!notifications) return <div>Loading...</div>;
return (
<div className="space-y-4">
{notifications.map((notification) => (
<div key={notification.id} className="p-4 border rounded-lg">
<h3 className="font-medium">{notification.subject}</h3>
<p>{notification.body}</p>
</div>
))}
{hasMore && <div ref={observerTarget} className="h-10" />}
</div>
);
}
The hook provides several actions to manage notifications:
import { useNotifications } from "@novu/react";
function NotificationManager() {
const { notifications, readAll, seenAll, archiveAll, archiveAllRead, isLoading } =
useNotifications();
const handleReadAll = async () => {
const { error } = await readAll();
if (error) {
console.error("Failed to mark all as read:", error);
}
};
const handleArchiveAll = async () => {
const { error } = await archiveAll();
if (error) {
console.error("Failed to archive all:", error);
}
};
const handleSeenAll = async () => {
const { error } = await seenAll();
if (error) {
console.error("Failed to mark all as seen:", error);
}
};
const handleArchiveAllRead = async () => {
const { error } = await archiveAllRead();
if (error) {
console.error("Failed to archive read notifications:", error);
}
};
return (
<div>
<div className="flex gap-2 mb-4">
<button
onClick={handleReadAll}
className="px-3 py-1 bg-blue-500 text-white rounded-md"
disabled={isLoading}
>
Mark All as Read
</button>
<button
onClick={handleSeenAll}
className="px-3 py-1 bg-green-500 text-white rounded-md"
disabled={isLoading}
>
Mark All as Seen
</button>
<button
onClick={handleArchiveAll}
className="px-3 py-1 bg-gray-500 text-white rounded-md"
disabled={isLoading}
>
Archive All
</button>
<button
onClick={handleArchiveAllRead}
className="px-3 py-1 bg-gray-500 text-white rounded-md"
disabled={isLoading}
>
Archive Read
</button>
</div>
<div className="space-y-4">
{notifications?.map((notification) => (
<div key={notification.id} className="p-4 border rounded-lg">
<h3 className="font-medium">{notification.subject}</h3>
<p>{notification.body}</p>
</div>
))}
</div>
</div>
);
}
The notifications list is automatically updated in real-time when new notifications arrive or when notifications state (read, seen, archived, snoozed) changes.