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,
notiflow: 'order-updates', // filter by notiflow handle
topic: 'order:123', // filter by topic
archived: true, // include archived entries
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.action_type === 'default' && entry.data.action_url) {
window.location.href = entry.data.action_url;
}
};
}usePreferences
Manage user notification preferences:
import { usePreferences } from '@notiflows/react';
function PreferencesPanel() {
const {
preferences, // Preferences | null
isLoading, // boolean
updateNotiflowPreferences, // (notiflowHandle, prefs) => Promise<void>
} = usePreferences();
if (isLoading) return <Spinner />;
return (
<div>
{Object.entries(preferences?.notiflows || {}).map(([handle, pref]) => (
<label key={handle}>
<input
type="checkbox"
checked={pref.enabled}
onChange={() =>
updateNotiflowPreferences(handle, {
...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>
);
}Allowed Origins
Before using the SDK in a browser, you must add your domain to the project's allowed origins list. Without this, the browser will block all API requests due to CORS.
See Allowed Origins for setup instructions.
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