Skip to main content

Using the useCourier Hook

The useCourier() hook provides programmatic access to Courier functionality, allowing you to build custom UIs or integrate Courier features into existing components. While the <CourierInbox /> and <CourierToast /> components provide ready-made UI, the hook gives you full control over authentication, message loading, real-time updates, and state management.
When to use hooks vs components: Use components for quick integration with default UI. Use hooks when you need custom UIs, programmatic control, or advanced patterns like custom state management. See the Inbox Component and Toast Component pages for ready-made UI components. For help deciding, see the When to Use Hooks vs Components guide in the overview.

Hook Return Value

The useCourier() hook returns an object with the following structure:
{
  shared: Courier.shared,  // Direct access to Courier instance
  auth: {
    userId?: string,
    signIn: (props: CourierProps) => void,
    signOut: () => void
  },
  inbox: {
    // Methods
    load: (props?: { canUseCache?: boolean, datasetIds?: string[] }) => Promise<void>,
    fetchNextPageOfMessages: (props: { datasetId: string }) => Promise<InboxDataSet | null>,
    setPaginationLimit: (limit: number) => void,
    registerFeeds: (feeds: CourierInboxFeed[]) => void,
    listenForUpdates: () => Promise<void>,
    readMessage: (message: InboxMessage) => Promise<void>,
    unreadMessage: (message: InboxMessage) => Promise<void>,
    clickMessage: (message: InboxMessage) => Promise<void>,
    archiveMessage: (message: InboxMessage) => Promise<void>,
    unarchiveMessage: (message: InboxMessage) => Promise<void>,
    openMessage: (message: InboxMessage) => Promise<void>,
    readAllMessages: () => Promise<void>,
    // Reactive state
    feeds: Record<string, InboxDataSet>,
    totalUnreadCount?: number,
    error?: Error
  },
  toast: {
    addMessage: (message: InboxMessage) => void,
    removeMessage: (message: InboxMessage) => void,
    error?: Error
  }
}

Authentication with Hooks

You can authenticate using either auth.signIn() from the hook or courier.shared.signIn() directly. Both methods work identically.
import { useEffect } from "react";
import { useCourier } from "@trycourier/courier-react";

export default function App() {
  const { auth } = useCourier();

  useEffect(() => {
    // Generate a JWT for your user on your backend server
    const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

    // Authenticate using the hook's auth object
    auth.signIn({
      userId: $YOUR_USER_ID,
      jwt: jwt,
      showLogs: false, // Optional: disable debug logs
    });
  }, []);

  return <div>Your app content</div>;
}
The showLogs option controls whether debug logs are printed to the console. Set to false in production to reduce console noise.

Setting Up the Inbox

To use the inbox programmatically, you need to:
  1. Register feeds to define which message views you want
  2. Start listening for real-time updates (required for real-time functionality)
  3. Load the initial messages
  4. Optionally fetch additional pages
Important: You must call inbox.listenForUpdates() after authentication to enable real-time message updates. Without this call, your inbox will only show messages on initial load and won’t update when new messages arrive.
import { useEffect } from "react";
import { useCourier, type InboxMessage, defaultFeeds } from "@trycourier/courier-react";

export default function App() {
  const { auth, inbox } = useCourier();

  useEffect(() => {
    // Authenticate the user
    auth.signIn({
      userId: $YOUR_USER_ID,
      jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    });

    // Load the inbox
    loadInbox();
  }, []);

  async function loadInbox() {
    // 1. Register feeds to define message views
    inbox.registerFeeds(defaultFeeds());

    // 2. Set up socket listener for real-time updates (REQUIRED)
    await inbox.listenForUpdates();

    // 3. Load the initial inbox data
    await inbox.load();

    // 4. Optionally fetch the next page of messages
    await fetchNextPageOfMessages();
  }

  async function fetchNextPageOfMessages() {
    const nextPage = await inbox.fetchNextPageOfMessages({ 
      datasetId: 'all_messages' 
    });
    if (nextPage && nextPage.canPaginate) {
      await fetchNextPageOfMessages();
    }
  }

  return <div>Your inbox UI</div>;
}

Accessing Reactive State

The inbox.feeds and inbox.totalUnreadCount properties are reactive and update automatically when messages change. You can use them directly in your JSX.
import { useCourier, type InboxMessage } from "@trycourier/courier-react";

export default function App() {
  const { inbox } = useCourier();

  return (
    <div>
      <div>Total Unread: {inbox.totalUnreadCount ?? 0}</div>
      <ul>
        {inbox.feeds['all_messages']?.messages.map((message: InboxMessage) => (
          <li key={message.messageId}>
            {message.read ? '✓' : '●'} {message.title}
          </li>
        ))}
      </ul>
    </div>
  );
}

Complete Example

Here’s a complete example that demonstrates authentication, inbox setup, real-time updates, pagination, and displaying messages:
import { useEffect } from 'react';
import { useCourier, type InboxMessage, defaultFeeds } from '@trycourier/courier-react';

export default function App() {
  const { auth, inbox } = useCourier();

  useEffect(() => {
    // Authenticate the user
    auth.signIn({
      userId: $YOUR_USER_ID,
      jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      showLogs: false,
    });

    // Load the inbox
    loadInbox();
  }, []);

  async function loadInbox() {
    // Register feeds
    inbox.registerFeeds(defaultFeeds());

    // Set up socket listener for real-time updates
    await inbox.listenForUpdates();

    // Load the initial inbox data
    await inbox.load();

    // Fetch the next page of messages if possible
    await fetchNextPageOfMessages();
  }

  async function fetchNextPageOfMessages() {
    const nextPage = await inbox.fetchNextPageOfMessages({ 
      datasetId: 'all_messages' 
    });
    if (nextPage && nextPage.canPaginate) {
      await fetchNextPageOfMessages();
    }
  }

  return (
    <div>
      <div style={{ padding: '24px' }}>
        Total Unread Count: {inbox.totalUnreadCount ?? 0}
      </div>
      <ul>
        {inbox.feeds['all_messages']?.messages.map((message: InboxMessage) => (
          <li 
            key={message.messageId} 
            style={{ 
              backgroundColor: message.read ? 'transparent' : '#fee2e2',
              padding: '8px',
              marginBottom: '4px'
            }}
          >
            {message.title}
          </li>
        ))}
      </ul>
    </div>
  );
}

Inbox Methods

Loading Messages

inbox.load(props?: { canUseCache?: boolean, datasetIds?: string[] }) Loads messages for all registered datasets or specific datasets.
  • canUseCache: If true (default), returns cached data if available. Set to false to force a fresh fetch.
  • datasetIds: Optional array of dataset IDs to load. If omitted, all registered datasets are loaded.
// Load all datasets with cache
await inbox.load();

// Force fresh load for all datasets
await inbox.load({ canUseCache: false });

// Load specific datasets only
await inbox.load({ datasetIds: ['inbox', 'archive'] });
inbox.fetchNextPageOfMessages(props: { datasetId: string }) Fetches the next page of messages for a specific dataset. Returns null if no more pages are available, or an InboxDataSet with canPaginate indicating if more pages exist.
const nextPage = await inbox.fetchNextPageOfMessages({ 
  datasetId: 'all_messages' 
});
if (nextPage?.canPaginate) {
  // More pages available
}
inbox.setPaginationLimit(limit: number) Sets the number of messages to fetch per page.
inbox.setPaginationLimit(50); // Fetch 50 messages per page

Managing Feeds

inbox.registerFeeds(feeds: CourierInboxFeed[]) Registers feeds and their tabs with the datastore. Each tab creates a dataset that filters messages according to the tab’s filter configuration.
import { defaultFeeds } from '@trycourier/courier-react';

// Use default feeds
inbox.registerFeeds(defaultFeeds());

// Or define custom feeds
inbox.registerFeeds([
  {
    feedId: 'notifications',
    title: 'Notifications',
    tabs: [
      {
        datasetId: 'all',
        title: 'All',
        filter: {}
      },
      {
        datasetId: 'unread',
        title: 'Unread',
        filter: { status: 'unread' }
      }
    ]
  }
]);
inbox.listenForUpdates() Establishes a WebSocket connection to receive real-time message updates. This must be called after authentication to enable real-time functionality.
await inbox.listenForUpdates();
Without calling listenForUpdates(), your inbox will not update in real-time when new messages arrive. The inbox will only show messages from the initial load.

Message Actions

inbox.readMessage(message: InboxMessage) Marks a message as read.
await inbox.readMessage(message);
inbox.unreadMessage(message: InboxMessage) Marks a message as unread.
await inbox.unreadMessage(message);
inbox.archiveMessage(message: InboxMessage) Archives a message.
await inbox.archiveMessage(message);
inbox.unarchiveMessage(message: InboxMessage) Unarchives a message.
await inbox.unarchiveMessage(message);
inbox.clickMessage(message: InboxMessage) Tracks a click event for a message (for analytics).
await inbox.clickMessage(message);
inbox.openMessage(message: InboxMessage) Marks a message as opened.
await inbox.openMessage(message);
inbox.readAllMessages() Marks all messages as read.
await inbox.readAllMessages();

Toast Methods

toast.addMessage(message: InboxMessage) Manually adds a message to the toast stack.
toast.addMessage(message);
toast.removeMessage(message: InboxMessage) Removes a message from the toast stack.
toast.removeMessage(message);

Error Handling

The inbox.error and toast.error properties contain any errors that occur during operations. Check these properties to handle errors in your UI.
import { useCourier } from "@trycourier/courier-react";

export default function App() {
  const { inbox, toast } = useCourier();

  if (inbox.error) {
    return <div>Error loading inbox: {inbox.error.message}</div>;
  }

  if (toast.error) {
    console.error("Toast error:", toast.error);
  }

  return <div>Your inbox UI</div>;
}

When to Use Hooks vs Components

Use Components (<CourierInbox />, <CourierToast />) when:
  • You want quick integration with default UI
  • You need standard inbox/toast functionality
  • You’re building a new feature and want to get started quickly
  • You want Courier to handle UI rendering
Use Hooks (useCourier()) when:
  • You need a custom UI that doesn’t match the default components
  • You want to integrate Courier features into existing components
  • You need programmatic control over message loading and state
  • You’re building advanced patterns like custom state management
  • You want to combine inbox data with other data sources
You can use both together:
  • Use hooks to manage state and components to render UI
  • Use hooks for programmatic actions while components handle display
  • Mix and match based on your needs
Sample Apps: See complete working examples in our React Hooks sample app and React Inbox sample app.