React
Notiflows React SDK with hooks and components for notification UI
The React SDK (@notiflows/react) provides React hooks and pre-built components for displaying notifications in your application. It's built on top of the JavaScript SDK.
Installation
npm install @notiflows/react
# or
pnpm add @notiflows/react
# or
yarn add @notiflows/reactQuick Start
Import the styles and wrap your application with NotiflowsProvider:
import '@notiflows/react/styles.css';
import {
NotiflowsProvider,
FeedRoot,
FeedTrigger,
FeedContent,
} from '@notiflows/react';
function App() {
return (
<NotiflowsProvider
apiKey="pk_your_public_key"
userId="user_123"
userKey="jwt_token_from_backend"
channelId="your-channel-id"
>
<Header />
<MainContent />
</NotiflowsProvider>
);
}
function Header() {
return (
<header>
<nav>
<FeedRoot>
<FeedTrigger />
<FeedContent />
</FeedRoot>
</nav>
</header>
);
}That's it! You now have a fully functional notification bell with a popover feed.
Styles
Default Styles
The SDK ships with a CSS file that provides ready-to-use styling:
import '@notiflows/react/styles.css';You must import the styles for the pre-built components to render correctly. Import it once in your app's entry point.
Customization with CSS Variables
Override CSS variables to match your brand:
:root {
/* Colors */
--nf-primary: #0066cc;
--nf-primary-foreground: #ffffff;
--nf-background: #ffffff;
--nf-foreground: #0f0f0f;
--nf-muted: #f5f5f5;
--nf-muted-foreground: #737373;
--nf-border: #e5e5e5;
--nf-popover: #ffffff;
--nf-popover-foreground: #0f0f0f;
/* Typography */
--nf-font-family: system-ui, sans-serif;
--nf-font-size-xs: 0.75rem;
--nf-font-size-sm: 0.875rem;
--nf-font-size-base: 1rem;
/* Spacing & Radius */
--nf-radius: 0.5rem;
--nf-radius-sm: 0.25rem;
/* Shadows */
--nf-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--nf-shadow-md: 0 10px 15px -3px rgb(0 0 0 / 0.1);
/* Special */
--nf-unread-indicator: #0891b2;
--nf-badge-bg: #ef4444;
--nf-badge-text: #ffffff;
}Dark Mode
Add the dark class to a parent element to enable dark mode:
<html class="dark">
<!-- Components automatically use dark colors -->
</html>The SDK includes dark mode styles that are activated when a parent element has the dark class.
Custom Classes
All components accept a className prop for additional styling:
<FeedContent className="my-custom-popover" />
<FeedPanel className="my-custom-panel" />Provider
The NotiflowsProvider initializes the client and provides context to all child components.
<NotiflowsProvider
// Required
apiKey="pk_your_public_key"
userId="user_123"
userKey="jwt_token_from_backend"
channelId="your-channel-id"
// Optional
apiUrl="https://api.notiflows.com/user/v1"
wsUrl="wss://api.notiflows.com/ws/v1"
>
{children}
</NotiflowsProvider>The userKey must be generated on your backend using your Application Signing Key. Never expose your signing key in client-side code. See Generating User Tokens for instructions.
Components
Feed Components
The feed components create a popover-based notification center:
import {
FeedRoot,
FeedTrigger,
FeedContent,
FeedPanel,
BellButton,
} from '@notiflows/react';
// Minimal setup - uses defaults
<FeedRoot>
<FeedTrigger /> {/* Renders BellButton by default */}
<FeedContent /> {/* Renders FeedPanel by default */}
</FeedRoot>
// Explicit setup
<FeedRoot>
<FeedTrigger>
<BellButton />
</FeedTrigger>
<FeedContent>
<FeedPanel />
</FeedContent>
</FeedRoot>FeedRoot
Wrapper component that manages the popover state. Wraps Radix UI Popover.
<FeedRoot
open={boolean} // Controlled open state
onOpenChange={fn} // Callback when open state changes
defaultOpen={boolean} // Initial open state (uncontrolled)
>
{children}
</FeedRoot>FeedTrigger
Button that opens the notification popover. Renders BellButton by default.
// Default
<FeedTrigger />
// Custom trigger
<FeedTrigger>
<button className="my-button">
<MyBellIcon />
<Badge />
</button>
</FeedTrigger>FeedContent
Popover content container. Renders FeedPanel by default.
// Default
<FeedContent />
// With custom alignment
<FeedContent align="end" sideOffset={8} />
// With custom content
<FeedContent>
<FeedPanel NotificationComponent={MyNotification} />
</FeedContent>FeedPanel
The main notification panel with tabs (All, Unread, Read) and settings.
<FeedPanel
NotificationComponent={CustomNotification} // Custom notification renderer
className="my-panel"
/>BellButton
Bell icon button with unread badge:
import { BellButton, Badge } from '@notiflows/react';
// Standalone usage
<BellButton onClick={handleClick} />
// Just the badge
<Badge totalUnread={5} />Notification
Individual notification item component:
import { Notification } from '@notiflows/react';
// Default rendering
<Notification item={entry} />
// With custom action handler
<Notification
item={entry}
onActionClick={(url, entry) => {
// Custom navigation logic
router.push(url);
}}
/>MarkAllAsRead
Button to mark all notifications as read:
import { MarkAllAsRead } from '@notiflows/react';
<MarkAllAsRead onClick={() => markAllAsRead()}>
Mark all as read
</MarkAllAsRead>MarkAsArchived
Archive button for individual notifications:
import { MarkAsArchived } from '@notiflows/react';
<MarkAsArchived item={entry} />Preferences
Preferences panel with toggles for each notiflow:
import { Preferences } from '@notiflows/react';
<Preferences />Avatar
User avatar with fallback:
import { Avatar } from '@notiflows/react';
<Avatar
src="https://example.com/avatar.jpg"
alt="John Doe"
fallback="JD"
/>Hooks
useNotiflows
Access the Notiflows client and feed client:
import { useNotiflows } from '@notiflows/react';
function MyComponent() {
const { client, feedClient } = useNotiflows();
// Direct API access
const handleGetPrefs = async () => {
const prefs = await client.userPreferences().get();
console.log(prefs);
};
}useFeed
Access the notification feed with automatic real-time updates:
import { useFeed } from '@notiflows/react';
function MyComponent() {
const {
entries, // FeedEntry[] - notification items
totalUnread, // number - unread count
totalUnseen, // number - unseen count
lastPage, // pagination info
isLoading, // boolean - initial load
isLoadingMore, // boolean - loading more items
error, // NotiflowsApiError | null
loadMore, // () => Promise<void>
clearError, // () => void
retry, // () => void
} = useFeed();
// With options
const { entries } = useFeed({
status: FeedEntryStatus.Unread,
limit: 20,
});
return (
<div>
<p>{totalUnread} unread notifications</p>
{entries.map(entry => (
<div key={entry.id}>{entry.data.body}</div>
))}
{lastPage?.has_more_after && (
<button onClick={loadMore} disabled={isLoadingMore}>
Load more
</button>
)}
</div>
);
}useNotificationStatus
Update notification states:
import { useNotificationStatus } from '@notiflows/react';
function MyComponent() {
const {
markAsSeen, // (entryId: string) => Promise<void>
markAsRead, // (entryId: string) => Promise<void>
markAsClicked, // (entryId: string) => Promise<void>
markAsArchived, // (entryId: string) => Promise<void>
markAsUnarchived, // (entryId: string) => Promise<void>
markAllAsRead, // () => Promise<void>
batchMarkAsRead, // (entryIds: string[]) => Promise<void>
batchMarkAsArchived, // (entryIds: string[]) => Promise<void>
} = useNotificationStatus();
const handleNotificationClick = async (entry: FeedEntry) => {
if (!entry.read_at) {
await markAsRead(entry.id);
}
if (entry.data.actionUrl) {
window.location.href = entry.data.actionUrl;
}
};
}usePreferences
Manage user notification preferences:
import { usePreferences } from '@notiflows/react';
function PreferencesPanel() {
const {
preferences, // Preferences | null
isLoading, // boolean
updateNotiflowPreferences, // (notiflowId, prefs) => Promise<void>
} = usePreferences();
if (isLoading) return <Spinner />;
return (
<div>
{Object.entries(preferences?.notiflows || {}).map(([id, pref]) => (
<label key={id}>
<input
type="checkbox"
checked={pref.enabled}
onChange={() =>
updateNotiflowPreferences(id, {
...pref,
enabled: !pref.enabled,
})
}
/>
{pref.name}
</label>
))}
</div>
);
}Building Custom UI
Use the hooks to build completely custom notification UIs:
import { useFeed, useNotificationStatus } from '@notiflows/react';
function CustomNotificationCenter() {
const { entries, totalUnread, isLoading, loadMore } = useFeed();
const { markAsRead, markAllAsRead } = useNotificationStatus();
if (isLoading) {
return <div className="loading">Loading...</div>;
}
return (
<div className="notification-center">
<header>
<h2>Notifications</h2>
<span className="badge">{totalUnread}</span>
<button onClick={markAllAsRead}>Mark all read</button>
</header>
<ul className="notification-list">
{entries.map(entry => (
<li
key={entry.id}
className={entry.read_at ? 'read' : 'unread'}
onClick={() => markAsRead(entry.id)}
>
{entry.actor && (
<img
src={entry.actor.avatar}
alt={entry.actor.first_name}
/>
)}
<div>
<p>{entry.data.body}</p>
<time>{formatTime(entry.created_at)}</time>
</div>
</li>
))}
</ul>
<button onClick={loadMore}>Load more</button>
</div>
);
}TypeScript
The SDK is fully typed. Import types as needed:
import type {
FeedEntry,
FeedEntryCollection,
FeedEntryStatus,
Preferences,
NotiflowsApiError,
} from '@notiflows/react';
// Re-exported from @notiflows/client
import {
isApiError,
BadRequestError,
UnauthenticatedError,
NotFoundError,
} from '@notiflows/react';Error Handling
Handle errors from hooks:
import { useFeed, isApiError } from '@notiflows/react';
function NotificationList() {
const { entries, error, retry, clearError } = useFeed();
if (error) {
return (
<div className="error">
<p>Failed to load notifications</p>
{isApiError(error) && <p>{error.message}</p>}
<button onClick={retry}>Retry</button>
<button onClick={clearError}>Dismiss</button>
</div>
);
}
return (
<ul>
{entries.map(entry => (
<li key={entry.id}>{entry.data.body}</li>
))}
</ul>
);
}Next.js Integration
App Router
Use the 'use client' directive for components that use the SDK:
// components/notifications.tsx
'use client';
import '@notiflows/react/styles.css';
import {
NotiflowsProvider,
FeedRoot,
FeedTrigger,
FeedContent,
} from '@notiflows/react';
interface NotificationsProps {
userKey: string;
userId: string;
}
export function Notifications({ userKey, userId }: NotificationsProps) {
return (
<NotiflowsProvider
apiKey={process.env.NEXT_PUBLIC_NOTIFLOWS_API_KEY!}
channelId={process.env.NEXT_PUBLIC_NOTIFLOWS_CHANNEL_ID!}
userKey={userKey}
userId={userId}
>
<FeedRoot>
<FeedTrigger />
<FeedContent />
</FeedRoot>
</NotiflowsProvider>
);
}// app/layout.tsx
import { Notifications } from '@/components/notifications';
import { auth, generateUserKey } from '@/lib/auth';
export default async function RootLayout({ children }) {
const session = await auth();
// Generate user key on the server
const userKey = await generateUserKey(session.user.id);
return (
<html lang="en">
<body>
<nav>
<Notifications
userKey={userKey}
userId={session.user.id}
/>
</nav>
{children}
</body>
</html>
);
}Pages Router
// pages/_app.tsx
import '@notiflows/react/styles.css';
// components/notifications.tsx
import {
NotiflowsProvider,
FeedRoot,
FeedTrigger,
FeedContent,
} from '@notiflows/react';
export function Notifications({ userKey, userId }) {
return (
<NotiflowsProvider
apiKey={process.env.NEXT_PUBLIC_NOTIFLOWS_API_KEY}
channelId={process.env.NEXT_PUBLIC_NOTIFLOWS_CHANNEL_ID}
userKey={userKey}
userId={userId}
>
<FeedRoot>
<FeedTrigger />
<FeedContent />
</FeedRoot>
</NotiflowsProvider>
);
}Browser Support
The SDK supports all modern browsers (ES2022+):
- Chrome 94+
- Firefox 93+
- Safari 15+
- Edge 94+
Next Steps
- JavaScript SDK - Lower-level client for custom integrations
- Authentication - Learn about user token generation
- API Reference - Full User API documentation