What’s new in React v8
v8 of the Courier React SDK is a major update to Courier’s web SDK ecosystem. The latest update features: - A re-designed, modern UI by default with built-in dark mode support
- Fully theme-able and customizable components
- A much smaller JavaScript bundle with no third-party dependencies
React Inbox v8 does not yet support the following: - Pins: Pinning a message to the top of the inbox
- Tags: Managing categories of messages
If your app requires these features, we recommend continuing to use v7 at this time. What’s in this guide
This guide includes migration steps for: Both follow the same path to upgrade dependencies and authentication. After those steps, you may skip ahead to Toasts if you don’t need to migrate Inbox components. Migration Steps
1. Upgrade Dependencies
Courier React v8 is available as a single package @trycourier/courier-react for React 18+ or @trycourier/courier-react-17 for React 17 support. The two packages contain the same functionality: the examples below work with either, and only one should be used in your app. Earlier versions of the Courier React SDKs required multiple packages (e.g. @trycourier/react-provider and @trycourier/react-inbox), which you should remove from your dependencies as part of this migration. npm install --save @trycourier/courier-react
In your app’s package.json, remove existing @trycourier React dependencies: { "dependencies": { "@trycourier/react-provider": "^7.4.0", "@trycourier/react-inbox": "^7.4.0", "@trycourier/react-toast": "^7.4.0", "@trycourier/courier-react": "^8.2.0", "react": "^19.1.1", "react-dom": "^19.1.1" } }
2. Generate JWTs
If you’re already generating JWTs (JSON Web Tokens) to authenticate Courier SDKs, you can skip this step.
Courier React v8 requires JWTs to authenticate connections to the Courier backends. JWTs are short-lived, signed tokens used to securely authenticate and authorize requests. They are the recommended way to connect to the Courier backend in all cases. Earlier verions of the Courier React SDKs accepted client keys (a stable Base64 encoded identifier) with or without an HMAC signature. JWT generation requires a private key and should be done in an environment where that key can be accessed securely, like your backend. A typical production JWT generation flow might look like this:
Your app calls your backend
When your app needs to authenticate a user, your app should make a request to your own backend (ex. GET https://my-awesome-app.com/api/generate-courier-jwt).
Your backend calls Courier
Your backend returns the JWT to your app
Having received the JWT from Courier, your backend should return it to your app and pass it to the Courier SDK.
Testing JWTs in Development
To quickly get up and running with JWTs in development, you can use cURL to call the Courier Issue Token Endpoint directly. curl --request POST \ --url https://api.courier.com/auth/issue-token \ --header 'Accept: application/json' \ --header 'Authorization: Bearer $YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data \ '{ "scope": "user_id:$YOUR_USER_ID write:user-tokens inbox:read:messages inbox:write:events read:preferences write:preferences read:brands", "expires_in": "$YOUR_NUMBER days" }'
3. Authenticate the Courier SDK
Replace instances of <CourierProvider> with a single call to courier.shared.signIn(), a method accessible through the singleton exposed with useCourier(). The Courier SDK shares authentication between components, so only one call to courier.shared.signIn is needed in an app.If you’re only using Courier toasts, skip ahead to Upgrade Toast Components to see this authentication example for <CourierToast>. import { CourierProvider } from "@trycourier/react-provider"; import { Inbox } from "@trycourier/react-inbox"; import { useEffect, useState } from "react"; import { CourierInbox, useCourier } from "@trycourier/courier-react"; function App() { const [courierJwt, setCourierJwt] = useState<string>(); const courier = useCourier(); const generateCourierJwt = async () => { // Call your backend to generate a JWT setCourierJwt(result); }; // When the userId in the app changes, // generate a new JWT to sign in to the Courier SDK useEffect(() => { generateCourierJwt(); }, [userId]); // When courierJwt has been updated, call signIn useEffect(() => { // Authenticate the user with the inbox courier.shared.signIn({ userId, jwt: courierJwt }); }, [courierJwt]); return ( <CourierProvider authentication={courierJwt} clientKey={clientKey}> <Inbox /> </CourierProvider> <CourierInbox /> ); }
4. Upgrade Inbox components
4a. Theming
v8 removes the dependency on styled-components and supports theming natively. A few examples of how the theme definition has changed are below. For a complete reference while migrating, see the v7 theme and the v8 theme types. import { Inbox } from "@trycourier/react-inbox"; import { CourierProvider } from "@trycourier/react-provider"; import { CourierInbox, CourierInboxTheme } from "@trycourier/courier-react"; function App() { // `CourierInboxTheme` is exposed in `@trycourier/courier-react` and may provide // helpful hints in your code editor. const theme: CourierInboxTheme = { // In v8, inbox theme values are nested under `inbox` inbox: { header: { background: "#0f0f0f", backgroundColor: "#0f0f0f" }, messageList: { container: { background: "red", }, }, list: { backgroundColor: "red", }, item: { backgroundColor: "purple", hoverBackgroundColor: "steelblue", }, }, // In v8, message theme values are nested under `inbox.item` message: { container: { background: "purple", }, "&:hover": { background: "steelblue", }, }, // The Courier watermark is removed in v8, // so footer theming is no longer applicable footer: { background: "pink", }, }; return ( <CourierProvider> <Inbox theme={theme} /> </CourierProvider> <Inbox lightTheme={theme} darkTheme={theme} /> ); }
Dark Mode
React Inbox v8 improves support for dark mode with automatic switching between light and dark modes based on system preferences. import { Inbox } from "@trycourier/react-inbox"; import { CourierProvider } from "@trycourier/react-provider"; import { CourierInbox, CourierInboxTheme } from "@trycourier/courier-react"; import { appTheme } from "./theme"; function App() { // In v8, the entire dark mode theme can be customized const darkThemeVariables = { background: "black", }; const lightTheme: CourierInboxTheme = { ...appTheme, // Light mode overrides... }; const darkTheme: CourierInboxTheme = { ...appTheme, // Dark mode overrides... }; return ( <CourierProvider theme={{ colorMode: "dark", variables: darkThemeVariables }}> <Inbox theme={darkTheme} /> </CourierProvider> // In v8, themes can be set for both light and dark mode // Use the `mode` prop to force the "light", "dark", or "system" color mode <CourierInbox lightTheme={lightTheme} darkTheme={darkTheme} mode="system" /> ); }
4b. Custom Components
Most components in the default UI can be overridden with custom components. Some prop names and method signatures to pass components have changed in v8. An example is shown here. See the Courier React docs for more examples of using custom components and customizing user interactions in React v8. import { Inbox } from "@trycourier/react-inbox"; import { CourierProvider } from "@trycourier/react-provider"; import { CourierInbox, type CourierInboxListItemFactoryProps } from '@trycourier/courier-react'; export default function App() { const CustomListItem = ( // In v8, the full message and its index within the inbox are available message => ( { message, index }: CourierInboxListItemFactoryProps) => ( <pre style={{ padding: '24px', borderBottom: '1px solid #e0e0e0', margin: '0' }}> {JSON.stringify({ message }, null, 2)} </pre> ); return ( <CourierProvider> <Inbox renderMessage={(props) => { return <CustomListItem {...props} /> }} /> </CourierProvider> <CourierInbox renderListItem={(props: CourierInboxListItemFactoryProps) => { return <CustomListItem {...props} /> }} /> ); }
Custom Component Prop Changes
Inbox Components: | v7 prop | v8 prop |
|---|
renderMessage | renderListItem |
renderNoMessages | renderEmptyState |
renderHeader | renderHeader |
| New in v8 | renderLoadingState |
| New in v8 | renderErrorState |
| New in v8 | renderPaginationItem |
renderPin | Pins are not yet supported in v8 |
renderFooter | The Courier watermark footer is removed in v8. |
4c. React Hooks
Message tags and pins are not yet supported in v8. If your app requires tagging or pinning we recommend continuing to use v7 at this time.
Use cases with custom UIs may require fetching messages and maintaining the inbox state manually via hooks. Courier’s React hooks are now included in @trycourier/courier-react. Remove any dependency on @trycourier/react-hooks. { "dependencies": { "@trycourier/react-hooks": "^7.4.0", "@trycourier/courier-react": "^8.0.28" } }
Update uses of useInbox() to useCourier() and update changed method signatures. A few common examples are shown here. The full set of updated hooks is below. import { useInbox } from "@trycourier/react-hooks"; import { useCourier } from "@trycourier/courier-react"; const MyCustomInbox = () => { const inbox = useInbox(); const { inbox } = useCourier(); useEffect(() => { inbox.fetchMessages(); inbox.load(); }, []); const handleRead = (message) => (event) => { event.preventDefault(); inbox.markMessageRead(message.messageId); inbox.readMessage({ message }); }; const handleUnread = (message) => (event) => { event.preventDefault(); inbox.markMessageUnread(message.messageId); inbox.unreadMessage(message) }; const handleArchive = (message) => (event) => { event.preventDefault(); inbox.markMessageArchived(message.messageId); inbox.archiveMessage(message); }; return ( <Container> {inbox.messages.map((message) => { return ( <Message message={message}> {message.read ? ( <> <button onClick={handleUnread(message)}>Mark Unread</button> <button onClick={handleArchive(message)}>Archive</button> </> ) : ( <button onClick={handleRead(message)}>Mark Read</button> )} </Message> ); })} </Container> ) }
Method Signature Changes
| v7 signature | v8 signature |
|---|
fetchMessages() | load() |
| New in v8 | setPaginationLimit(limit: number) |
| New in v8 | fetchNextPageOfMessages({ feedType: 'inbox' | 'archive' }) |
getUnreadMessageCount() | Replaced with load() (async, refresh the inbox) and inbox.unreadCount (sync, reads stored state) |
markMessageRead(messageId: string) | readMessage(message: InboxMessage) |
markMessageUnread(messageId: string) | unreadMessage(message: InboxMessage) |
markMessageArchived(messageId: string) | archiveMessage(message: InboxMessage) |
| New in v8 | unarchiveMessage(message: InboxMessage) |
trackClick(messageId: string, trackingId: string) | clickMessage(message: InboxMessage) |
markMessageOpened(messageId: string) | openMessage(message: InboxMessage) |
markAllAsRead() | readAllMessages() |
addTag(messageId: string, tag: string) | Not yet supported in v8 |
removeTag(messageId: string, tag: string) | Not yet supported in v8 |
unpinMessage(messageId: string) | Not yet supported in v8 |
5. Upgrade Toast Components
Courier React v8 includes a redesigned Toast component with improved customization, theming, and dark mode support. The migration from v7 Toast involves updating the component usage, props, and theming structure. Authenticating the SDK for Toasts Replace instances of <CourierProvider> with a single call to courier.shared.signIn(), a method accessible through the singleton exposed with useCourier(). If you’ve already authenticated for Courier Inbox, you only need to replace blocks of <CourierProvider>...</CourierProvider> with the <CourierToast> component.The Courier SDK shares authentication between components, so only one call to courier.shared.signIn is needed in an app.
import { CourierProvider } from "@trycourier/react-provider"; import { Toast } from "@trycourier/react-toast"; import { useEffect, useState } from 'react'; import { CourierToast, useCourier } from "@trycourier/courier-react"; function App() { const [courierJwt, setCourierJwt] = useState<string>(); const courier = useCourier(); const generateCourierJwt = async () => { // Call your backend to generate a JWT setCourierJwt(result); }; // When the userId in the app changes, // generate a new JWT to sign in to the Courier SDK useEffect(() => { generateCourierJwt(); }, [userId]); // When courierJwt has been updated, call signIn useEffect(() => { // Authenticate the user courier.shared.signIn({ userId, jwt: courierJwt }); }, [courierJwt]); return ( <CourierProvider authentication={courierJwt} clientKey={clientKey}> <Toast /> </CourierProvider> <CourierToast /> ); }
5a. Toast Props
Some props passed to <CourierToast> to configure behavior have changed or moved in v8. Below is the mapping from v7 to v8. Props for theming, custom components, and handling user interaction are detailed below. | v7 | v8 | Notes |
|---|
autoClose | autoDismiss and autoDismissTimeoutMs | In v8, split into two separate props. autoDismiss is a boolean |
defaultIcon | Removed in v8 | In v8, configured via theme object. See theming below |
hideProgressBar | autoDismiss | In v8, progress bar only displays when autoDismiss is enabled |
| New in v8 | dismissButton | Display option for the dismiss button |
onClick | onToastItemClick | Event signature has changed in v8 |
| New in v8 | onToastItemActionClick | Handle an action button click |
position | style | Use React’s CSS-like style prop on <CourierToast> |
theme | lightTheme and darkTheme | v8 introduces separate themes for light and dark modes |
transition | Removed in v8 | Configure custom transitions by implementing renderToastItem |
role | Not yet supported in v8 | |
| New in v8 | renderToastItemContent | Customize the toast item content area. See custom components below |
| New in v8 | renderToastItem | Completely customize the toast items and stack. See custom components below |
5b. Theming
v8 removes the dependency on the styled-components package and supports theming natively. A few examples of how the theme definition has changed are below. For a complete reference while migrating, see the v7 theme and v8 theme types. import { Inbox } from "@trycourier/react-inbox"; import { CourierProvider } from "@trycourier/react-provider"; import { CourierInbox, CourierInboxTheme } from "@trycourier/courier-react"; function App() { // `CourierToastTheme` is exposed in `@trycourier/courier-react` // and may provide helpful hints in your code editor. const theme: CourierToastTheme = { // Styles applied to the top-level container (ex. z-index or position) // In v8 these are applied with the `style` prop on <CourierToast> root: { top: "10px", right: "10px", }, // Styles for toast items' outer container // In v8 this is configurable under `item` toast: {} // Styles for message contents // In v8 these are nested as values under `item` message: { container: { background: "white" }, title: { color: "black" } }, // Styles for the progress bar // In v8 this is nested under `item.autoDismissBarColor` progressBar: { background: "purple" }, item { backgroundColor: "white", title: { color: "black" }, autoDismissBarColor: "purple" }, }; return ( <CourierProvider> <Toast theme={theme} /> </CourierProvider> <CourierToast lightTheme={theme} darkTheme={theme} style={{ top: "10px", right: "10px" }} /> ); }
Dark Mode
React Inbox v8 improves support for dark mode with fully customizable theming and automatic switching between light and dark modes based on system preferences. For a complete reference while migrating, see the Courier React v8 theme type. import { Inbox } from "@trycourier/react-inbox"; import { CourierProvider } from "@trycourier/react-provider"; import { CourierToast, CourierToastTheme } from "@trycourier/courier-react"; import { appTheme } from "./theme"; function App() { const lightTheme: CourierToastTheme = { ...appTheme, // Light mode overrides... }; const darkTheme: CourierToastTheme = { ...appTheme, // Dark mode overrides... }; return ( <CourierProvider> <Toast theme={darkTheme} /> </CourierProvider> // In v8, themes can be set for both light and dark mode // Use the `mode` prop to force the "light", "dark", or "system" color mode <CourierToast lightTheme={lightTheme} darkTheme={darkTheme} mode="system" /> ); }
5c. Custom Components
v8 introduces two render props to use components when your integration requires customization beyond theming: renderToastItemContent: Customize the content area while keeping the default styles, animations, and dismiss functionality. renderToastItem: Customize the complete appearance and behavior of the toast items and stack.
See the Courier React docs for more examples of using custom components and customizing user interactions in React v8. 5d. React Hooks
Use cases with custom UIs may require maintaining the toast state manually via hooks. Courier’s React hooks are now included in @trycourier/courier-react. Remove any dependency on @trycourier/react-hooks. { "dependencies": { "@trycourier/react-hooks": "^7.4.0", "@trycourier/courier-react": "^8.0.28" } }
Update uses of useToast() to useCourier() and update changed method signatures. Example usage and the full set of updated hooks is below. import { useToast } from "@trycourier/react-hooks"; import { useCourier } from "@trycourier/courier-react"; const MyCustomToast = () => { const [toast] = useToast(); const { toast } = useCourier(); useEffect(() => { toast({ toast.addMessage({ title: "Hello, world!", preview: "This is a toast." }); }, []) return ( <CourierToast onToastItemClick={({ message } => { // New in v8 toast.removeMessage(message); })} /> ); }
Method Signature Changes
| v7 signature | v8 signature |
|---|
toast(message) | toast.addMessage(message: InboxMessage) |
| New in v8 | toast.removeMessage(message: InboxMessage) |
config and clientKey info are removed from hooks in v8.
v7 Documentation
Documentation for v7 and below of the Courier React SDKs can be found at: Markdown Rendering
Courier React versions 1.13.0 through 7.x.x automatically render Markdown-formatted messages, such as those output when Markdown Rendering is enabled for the Courier Inbox provider. v8 of the Courier React SDK does not include Markdown rendering support by default, however it can be implemented with custom Inbox components. The following example uses the markdown-to-jsx package. { "dependencies": { "@trycourier/courier-react": "^8.0.28", "markdown-to-jsx": "^7.7.13", "react": "^19.1.1", "react-dom": "^19.1.1" } }
import { CourierInbox, type CourierInboxListItemFactoryProps } from '@trycourier/courier-react'; import Markdown from "markdown-to-jsx"; export default function App() { return ( <CourierInbox renderListItem={(props: CourierInboxListItemFactoryProps) => { return ( <Markdown>{ props.message.preview || "Empty message" }</Markdown> ); }} /> ); }
Questions, answers, and troubleshooting info that may come up while migrating. Can I migrate one of Inbox or Toasts without migrating the other? You must migrate both at the same time. The React v8 SDK depends on a newer major version of the @trycourier/courier-js SDK than previous Courier React SDKs. Running both versions on the same page is not recommended and may result in unexpected issues. I don’t see any requests to the Courier backend after upgrading. This may be caused by multiple versions of the upgraded packages installed at once. Make sure only the latest version of each @trycourier/ package is installed. You may need to run npm dedupe, yarn dedupe or the equivalent command for your package manager to remove older conflicting versions. See github.com/@trycourier/courier-web/issues/92 for more information. If you have more questions while upgrading, please open an issue on GitHub or reach out to Courier Support through the Help Center.