React SDK
React hooks for channels, messages, real-time subscriptions, and publishing
The @npayload/react SDK provides React hooks for building real-time interfaces on top of npayload. It handles deduplication, optimistic updates, and offline queuing automatically.
Installation
npm install @npayload/react @npayload/jspnpm add @npayload/react @npayload/jsyarn add @npayload/react @npayload/jsbun add @npayload/react @npayload/jsProvider setup
Wrap your app with NPayloadProvider to make all hooks available.
import { NPayloadProvider } from '@npayload/react';
export default function App({ children }: { children: React.ReactNode }) {
return (
<NPayloadProvider
config={{
publishableKey: process.env.NEXT_PUBLIC_NPAYLOAD_KEY!,
}}
>
{children}
</NPayloadProvider>
);
}For authenticated access (server-side token exchange):
<NPayloadProvider
config={{
getToken: async () => {
const res = await fetch('/api/npayload-token');
const { token } = await res.json();
return token;
},
}}
>
{children}
</NPayloadProvider>Hooks
useNPayload
Access the underlying client instance.
import { useNPayload } from '@npayload/react';
function MyComponent() {
const npayload = useNPayload();
// Use npayload client directly
}useChannels
List and manage channels.
import { useChannels } from '@npayload/react';
function ChannelList() {
const { channels, isLoading, error } = useChannels();
if (isLoading) return <p>Loading...</p>;
return (
<ul>
{channels.map((ch) => (
<li key={ch.gid}>{ch.name} ({ch.status})</li>
))}
</ul>
);
}useChannel
Get a single channel with real-time updates.
import { useChannel } from '@npayload/react';
function ChannelDetail({ name }: { name: string }) {
const { channel, isLoading } = useChannel(name);
if (isLoading || !channel) return null;
return (
<div>
<h2>{channel.name}</h2>
<p>Messages: {channel.stats?.messageCount}</p>
<p>Privacy: {channel.privacy}</p>
</div>
);
}useMessages
List messages with pagination.
import { useMessages } from '@npayload/react';
function MessageList({ channel }: { channel: string }) {
const { messages, isLoading, loadMore, hasMore } = useMessages(channel);
return (
<div>
{messages.map((msg) => (
<div key={msg.gid}>
<pre>{JSON.stringify(msg.payload, null, 2)}</pre>
<span>{msg.status}</span>
</div>
))}
{hasMore && <button onClick={loadMore}>Load more</button>}
</div>
);
}usePublish
Publish messages with optimistic updates.
import { usePublish } from '@npayload/react';
function PublishForm({ channel }: { channel: string }) {
const { publish, isPublishing } = usePublish(channel);
const handleSubmit = async (data: unknown) => {
await publish({ payload: data });
};
return (
<button onClick={() => handleSubmit({ event: 'click' })} disabled={isPublishing}>
{isPublishing ? 'Publishing...' : 'Publish'}
</button>
);
}useRealtime
Subscribe to real-time WebSocket updates.
import { useRealtime } from '@npayload/react';
function LiveFeed({ channel }: { channel: string }) {
const { messages } = useRealtime(channel, {
onMessage: (msg) => {
console.log('New message:', msg.payload);
},
});
return (
<div>
{messages.map((msg) => (
<div key={msg.gid}>{JSON.stringify(msg.payload)}</div>
))}
</div>
);
}useSubscriptions
Manage subscriptions for a channel.
import { useSubscriptions } from '@npayload/react';
function SubscriptionManager({ channel }: { channel: string }) {
const { subscriptions, isLoading } = useSubscriptions(channel);
return (
<ul>
{subscriptions.map((sub) => (
<li key={sub.gid}>
{sub.name} ({sub.status})
</li>
))}
</ul>
);
}useDeliveries
Track delivery status for a message.
import { useDeliveries } from '@npayload/react';
function DeliveryStatus({ messageId }: { messageId: string }) {
const { deliveries, isLoading } = useDeliveries(messageId);
return (
<ul>
{deliveries.map((del) => (
<li key={del.id}>
{del.subscriptionName}: {del.status} ({del.attemptCount} attempts)
</li>
))}
</ul>
);
}useDLQ
Manage dead letter queue entries.
import { useDLQ } from '@npayload/react';
function DLQViewer({ subscriptionId }: { subscriptionId: string }) {
const { entries, replay, replayAll, isLoading } = useDLQ(subscriptionId);
return (
<div>
{entries.map((entry) => (
<div key={entry.id}>
<pre>{JSON.stringify(entry.payload, null, 2)}</pre>
<button onClick={() => replay(entry.id)}>Replay</button>
</div>
))}
<button onClick={replayAll}>Replay all</button>
</div>
);
}TypeScript
All hooks are fully typed. Use generics for type-safe payloads:
interface OrderEvent {
event: 'order.created' | 'order.paid';
orderId: string;
total: number;
}
const { messages } = useMessages<OrderEvent>('orders');
// messages[0].payload is typed as OrderEventServer components (Next.js)
For Next.js App Router, use a hybrid approach. Fetch initial data on the server, then hydrate with hooks on the client:
// app/channels/page.tsx (server component)
import { NPayloadClient } from '@npayload/node';
import { ChannelList } from './channel-list';
export default async function ChannelsPage() {
const npayload = new NPayloadClient({ /* server auth */ });
const channels = await npayload.channels.list();
return <ChannelList initialData={channels} />;
}// app/channels/channel-list.tsx (client component)
'use client';
import { useChannels } from '@npayload/react';
export function ChannelList({ initialData }) {
const { channels } = useChannels({ initialData });
// Real-time updates on top of server-fetched data
}Next steps
- Node.js SDK for server-side operations
- Browser SDK for vanilla JavaScript
- Quickstart to see the full flow
Was this page helpful?