Skip to main content
npayload is launching soon.
npayloadDocs
SDKs

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/js
pnpm add @npayload/react @npayload/js
yarn add @npayload/react @npayload/js
bun add @npayload/react @npayload/js

Provider 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 OrderEvent

Server 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

Was this page helpful?

On this page