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:
- Register feeds to define which message views you want
- Start listening for real-time updates (required for real-time functionality)
- Load the initial messages
- 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