A lightweight, UI-agnostic notification management library for React applications with TypeScript support.
- Features
- Installation
- Demo
- Quick Start
- Core Concepts
- Advanced Hooks
- API Reference
- TypeScript Support
- Error Handling
- Advanced Usage Examples
- Contributing
- License
- Community
- 🔄 Complete State Management: Comprehensive notification state handling out of the box
- 🎣 Flexible Hooks: Customizable hooks for notification functionality
- ⏱️ Automatic Polling: Built-in polling system with configurable intervals
- 💾 Persistence: Optional local/session storage integration
- 🔍 Filtering & Grouping: Powerful utilities for organizing notifications
- 📊 TypeScript Support: Full type definitions for improved developer experience
- 🔄 Async Support: First-class async/await support with robust error handling
- 🔁 Retry Mechanism: Configurable retry logic for failed API calls
- 🎨 UI-Agnostic: Bring your own UI components
# Using npm
npm install react-notification-core
# Using yarn
yarn add react-notification-core
# Using pnpm
pnpm add react-notification-core
See React Notification Core in action with our example implementations:
- Basic Example: Live Demo | Source Code
- Advanced Example: Live Demo | Source Code
Here's a basic implementation to get you started:
import React from 'react'
import { NotificationProvider, useNotifications } from 'react-notification-core'
// API functions to fetch and manage notifications
const fetchNotifications = async () => {
const response = await fetch('/api/notifications')
return response.json()
}
const markAsRead = async (id) => {
await fetch(`/api/notifications/${id}/read`, { method: 'PUT' })
}
const markAllAsRead = async () => {
await fetch('/api/notifications/read-all', { method: 'PUT' })
}
const deleteNotification = async (id) => {
await fetch(`/api/notifications/${id}`, { method: 'DELETE' })
}
// Your notification UI component
function NotificationList() {
const {
notifications,
unreadCount,
markAsRead,
markAllAsRead,
deleteNotification,
refreshNotifications,
isLoading,
error,
} = useNotifications()
if (isLoading) return <div>Loading notifications...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
<header>
<h2>Notifications ({unreadCount} unread)</h2>
<div>
<button onClick={refreshNotifications}>Refresh</button>
<button onClick={markAllAsRead}>Mark all as read</button>
</div>
</header>
{notifications.length === 0 ? (
<p>No notifications</p>
) : (
<ul>
{notifications.map((notification) => (
<li key={notification.id} className={notification.read ? 'read' : 'unread'}>
<h3>{notification.title}</h3>
<p>{notification.message}</p>
<div>
{!notification.read && (
<button onClick={() => markAsRead(notification.id)}>Mark as read</button>
)}
<button onClick={() => deleteNotification(notification.id)}>Delete</button>
</div>
</li>
))}
</ul>
)}
</div>
)
}
// App setup with provider
function App() {
return (
<NotificationProvider
fetchNotifications={fetchNotifications}
onMarkAsRead={markAsRead}
onMarkAllAsRead={markAllAsRead}
onDeleteNotification={deleteNotification}
fetchOptions={{
retryCount: 3,
retryDelay: 1000,
timeout: 10000,
}}
>
<NotificationList />
</NotificationProvider>
)
}
export default App
The NotificationProvider
is the central component that manages notification state and provides context to child components.
Prop | Type | Required | Description |
---|---|---|---|
fetchNotifications |
() => Promise<Notification[]> |
Yes | Function to fetch notifications from your API |
onMarkAsRead |
(id: string) => Promise<void> |
No | Function to mark a notification as read |
onMarkAllAsRead |
() => Promise<void> |
No | Function to mark all notifications as read |
onDeleteNotification |
(id: string) => Promise<void> |
No | Function to delete a notification |
fetchOptions |
FetchOptions |
No | Options for fetch behavior (retries, timeout) |
initialState |
NotificationState |
No | Initial state for notifications |
children |
ReactNode |
Yes | Child components |
<NotificationProvider
fetchNotifications={fetchNotificationsFunction}
onMarkAsRead={markAsReadFunction}
onMarkAllAsRead={markAllAsReadFunction}
onDeleteNotification={deleteNotificationFunction}
fetchOptions={{
retryCount: 3,
retryDelay: 1000,
timeout: 10000,
}}
initialState={{
notifications: [], // Initial notifications
unreadCount: 0,
}}
>
<YourApp />
</NotificationProvider>
The primary hook to access and manage notifications from anywhere in your component tree.
Property | Type | Description |
---|---|---|
notifications |
Notification[] |
Array of all notifications |
unreadCount |
number |
Count of unread notifications |
isLoading |
boolean |
Loading state |
error |
Error | null |
Error state |
lastUpdated |
Date | null |
Timestamp of last update |
addNotification |
(notification: Notification) => void |
Function to add a new notification |
markAsRead |
(id: string) => Promise<void> |
Function to mark a notification as read |
markAllAsRead |
() => Promise<void> |
Function to mark all notifications as read |
deleteNotification |
(id: string) => Promise<void> |
Function to delete a notification |
clearAllNotifications |
() => void |
Function to clear all notifications |
refreshNotifications |
() => Promise<void> |
Function to refresh notifications |
function NotificationBadge() {
const { unreadCount } = useNotifications()
return <div className="badge">{unreadCount > 0 && <span>{unreadCount}</span>}</div>
}
Set up automatic polling for new notifications.
Option | Type | Default | Description |
---|---|---|---|
enabled |
boolean |
true |
Whether polling is enabled |
interval |
number |
60000 |
Polling interval in ms |
onError |
(error: Error) => void |
- | Error handler |
retryCount |
number |
3 |
Number of retries on failure |
retryDelay |
number |
1000 |
Delay between retries in ms |
Property | Type | Description |
---|---|---|
refresh |
() => Promise<void> |
Function to manually refresh |
stopPolling |
() => void |
Function to stop polling |
startPolling |
() => void |
Function to start polling |
isPolling |
() => boolean |
Function that returns current polling state |
function NotificationContainer() {
const { notifications } = useNotifications()
const { stopPolling, startPolling, isPolling } = useNotificationPolling({
interval: 30000, // Check every 30 seconds
onError: (err) => console.error('Polling error:', err),
})
return (
<div>
<button onClick={isPolling() ? stopPolling : startPolling}>
{isPolling() ? 'Pause Updates' : 'Enable Updates'}
</button>
<NotificationList notifications={notifications} />
</div>
)
}
Persist notifications to localStorage or sessionStorage.
Option | Type | Default | Description |
---|---|---|---|
storageKey |
string |
'notifications' |
Key to use in storage |
useSessionStorage |
boolean |
false |
Whether to use sessionStorage instead of localStorage |
Property | Type | Description |
---|---|---|
clearStorage |
() => void |
Function to clear storage |
function NotificationSettings() {
const { clearStorage } = useNotificationStorage({
storageKey: 'user-notifications',
})
return (
<div>
<h3>Notification Settings</h3>
<button onClick={clearStorage}>Clear Notification History</button>
</div>
)
}
Filter notifications based on various criteria.
Property | Type | Description |
---|---|---|
filteredNotifications |
Notification[] |
Array of filtered notifications |
filterByReadStatus |
(read: boolean) => void |
Function to filter by read status |
filterByType |
(type: string) => void |
Function to filter by type |
filterBySearch |
(term: string) => void |
Function to filter by search term |
filterByDateRange |
(start: Date, end: Date) => void |
Function to filter by date range |
resetFilters |
() => void |
Function to reset all filters |
countsByType |
Record<string, number> |
Object with counts by notification type |
totalCount |
number |
Total count of notifications |
filteredCount |
number |
Count of filtered notifications |
unreadCount |
number |
Count of unread notifications |
function NotificationFilters() {
const { filterByReadStatus, filterByType, resetFilters, countsByType } = useNotificationFilters()
return (
<div className="filters">
<button onClick={() => filterByReadStatus(false)}>Show Unread Only</button>
<button onClick={() => filterByReadStatus(true)}>Show Read Only</button>
<div className="type-filters">
{Object.entries(countsByType).map(([type, count]) => (
<button key={type} onClick={() => filterByType(type)}>
{type} ({count})
</button>
))}
</div>
<button onClick={resetFilters}>Clear Filters</button>
</div>
)
}
Group notifications by date or custom criteria.
Property | Type | Description |
---|---|---|
groupedNotifications |
Record<string, Notification[]> |
Object with grouped notifications |
groups |
string[] |
Array of group keys |
getNotificationsForGroup |
(group: string) => Notification[] |
Function to get notifications for a group |
groupedByType |
Record<string, Notification[]> |
Object with notifications grouped by type |
function GroupedNotifications() {
const { groupedNotifications, groups } = useNotificationGroups()
return (
<div>
{groups.map((group) => (
<div key={group}>
<h3>{group}</h3>
<ul>
{groupedNotifications[group].map((notification) => (
<li key={notification.id}>{notification.title}</li>
))}
</ul>
</div>
))}
</div>
)
}
The library provides several utility functions for working with notifications:
function fetchWithRetry<T>(
fn: () => Promise<T>,
options?: {
retryCount?: number
retryDelay?: number
timeout?: number
}
): Promise<T>
Executes an async function with retry capability on failure.
function formatTimestamp(timestamp: Date | string | number, format?: string): string
Formats a timestamp into a human-readable string.
function groupNotificationsByDate(
notifications: Notification[],
options?: {
format?: string
groupingFn?: (date: Date) => string
}
): Record<string, Notification[]>
Groups notifications by date with customizable formatting.
function filterNotifications(
notifications: Notification[],
filters: {
read?: boolean
type?: string
search?: string
dateRange?: { start: Date; end: Date }
}
): Notification[]
Filters notifications based on specified criteria.
This library is built with TypeScript and provides full type definitions:
// Base notification type
interface Notification {
id: string
title: string
message: string
read: boolean
timestamp: string | number | Date
type?: string
[key: string]: any // Additional custom properties
}
// Provider context type
interface NotificationContextType {
state: NotificationState
dispatch: React.Dispatch<NotificationAction>
api: {
fetchNotifications: () => Promise<Notification[]>
markAsRead?: (id: string) => Promise<void>
markAllAsRead?: () => Promise<void>
deleteNotification?: (id: string) => Promise<void>
}
fetchOptions: FetchOptions
}
// Notification state
interface NotificationState {
notifications: Notification[]
unreadCount: number
isLoading: boolean
error: Error | null
lastUpdated: Date | null
}
// Fetch options for API calls
interface FetchOptions {
retryCount?: number
retryDelay?: number
timeout?: number
}
// Polling hook options
interface UseNotificationPollingOptions {
enabled?: boolean
interval?: number
onError?: (error: Error) => void
retryCount?: number
retryDelay?: number
}
You can extend the base Notification
interface for custom notification types:
import { Notification } from 'react-notification-core'
// Extend with your custom properties
interface TaskNotification extends Notification {
priority: 'low' | 'medium' | 'high'
dueDate: string
assignedTo: string
project: string
}
The library includes built-in error handling with retry capability:
// Example with custom error handling
function NotificationsErrorHandler() {
const { error, isLoading, refreshNotifications } = useNotifications()
useEffect(() => {
if (error) {
// Show toast notification
toast.error(`Failed to load notifications: ${error.message}`)
}
}, [error])
if (isLoading) return <LoadingSpinner />
if (error) {
return (
<div className="error-container">
<p>Could not load notifications</p>
<button onClick={() => refreshNotifications()}>Try Again</button>
</div>
)
}
return <NotificationList />
}
import { NotificationProvider, useNotifications } from 'react-notification-core'
import type { Notification } from 'react-notification-core'
// Extend the base Notification type
interface CustomNotification extends Notification {
priority: 'low' | 'medium' | 'high'
category: string
actions?: Array<{
label: string
action: string
}>
}
// Use the custom type in your components
function NotificationList() {
const { notifications } = useNotifications()
const handleAction = (action: string, id: string) => {
// Handle different action types
switch (action) {
case 'approve':
approveRequest(id)
break
case 'reject':
rejectRequest(id)
break
default:
console.log(`Action ${action} for notification ${id}`)
}
}
return (
<ul className="notification-list">
{notifications.map((notification) => {
// Cast to custom type
const customNotification = notification as CustomNotification
return (
<li
key={notification.id}
className={`
notification-item
priority-${customNotification.priority}
category-${customNotification.category.toLowerCase()}
${notification.read ? 'read' : 'unread'}
`}
>
<h3>{customNotification.title}</h3>
<p>{customNotification.message}</p>
<span className="category">{customNotification.category}</span>
<div className="actions">
{customNotification.actions?.map((action) => (
<button
key={action.action}
onClick={() => handleAction(action.action, notification.id)}
className={`action-btn ${action.action}`}
>
{action.label}
</button>
))}
</div>
</li>
)
})}
</ul>
)
}
import { NotificationProvider } from 'react-notification-core'
import { initializeFirebase, getFirebaseMessaging } from './firebaseConfig'
function App() {
// Initialize Firebase for push notifications
useEffect(() => {
initializeFirebase()
// Request permission and get token
const messaging = getFirebaseMessaging()
messaging
.requestPermission()
.then(() => {
return messaging.getToken()
})
.then((token) => {
// Send token to your server
registerDeviceToken(token)
})
// Handle incoming notifications
messaging.onMessage((payload) => {
// You can add the notification to your local state
addNotification({
id: payload.messageId,
title: payload.notification.title,
message: payload.notification.body,
read: false,
timestamp: new Date(),
type: payload.data?.type || 'default',
// Add any custom data from payload.data
...payload.data,
})
})
}, [])
// API handlers
const fetchNotifications = async () => {
const response = await fetch('/api/notifications')
return response.json()
}
// Other handlers...
return (
<NotificationProvider
fetchNotifications={fetchNotifications}
// Other props...
>
<YourApp />
</NotificationProvider>
)
}
import { useEffect } from 'react'
import { NotificationProvider, useNotifications } from 'react-notification-core'
function WebSocketNotificationHandler() {
const { addNotification } = useNotifications()
useEffect(() => {
// Create WebSocket connection
const socket = new WebSocket('wss://your-api.com/notifications')
// Connection opened
socket.addEventListener('open', (event) => {
console.log('Connected to notification server')
})
// Listen for messages
socket.addEventListener('message', (event) => {
const notification = JSON.parse(event.data)
addNotification({
...notification,
read: false,
timestamp: notification.timestamp || new Date(),
})
})
// Handle errors
socket.addEventListener('error', (event) => {
console.error('WebSocket error:', event)
})
// Clean up on unmount
return () => {
socket.close()
}
}, [addNotification])
return null // This component doesn't render anything
}
function App() {
// API functions
const fetchNotifications = async () => {
// Your implementation
}
return (
<NotificationProvider fetchNotifications={fetchNotifications}>
<WebSocketNotificationHandler />
<YourApp />
</NotificationProvider>
)
}
Contributions are welcome! Please see our contributing guidelines for more details.
- Fork the repository
- Create your feature branch:
git checkout -b feature/amazing-feature
- Commit your changes:
git commit -m 'Add some amazing feature'
- Push to the branch:
git push origin feature/amazing-feature
- Open a Pull Request
MIT
Built with ❤️ by Benjtalkshow