Skip to main content

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: JWT Authentication Flow Diagram
1

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).
2

Your backend calls Courier

In your backend, use your Courier API Key to call the Courier Issue Token Endpoint and generate a JWT for the user.
3

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 propv8 prop
renderMessagerenderListItem
renderNoMessagesrenderEmptyState
renderHeaderrenderHeader
New in v8renderLoadingState
New in v8renderErrorState
New in v8renderPaginationItem
renderPinPins are not yet supported in v8
renderFooterThe 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.
package.json
{
  "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 signaturev8 signature
fetchMessages()load()
New in v8setPaginationLimit(limit: number)
New in v8fetchNextPageOfMessages({ 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 v8unarchiveMessage(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.
v7v8Notes
autoCloseautoDismiss and autoDismissTimeoutMsIn v8, split into two separate props. autoDismiss is a boolean
defaultIconRemoved in v8In v8, configured via theme object. See theming below
hideProgressBarautoDismissIn v8, progress bar only displays when autoDismiss is enabled
New in v8dismissButtonDisplay option for the dismiss button
onClickonToastItemClickEvent signature has changed in v8
New in v8onToastItemActionClickHandle an action button click
positionstyleUse React’s CSS-like style prop on <CourierToast>
themelightTheme and darkThemev8 introduces separate themes for light and dark modes
transitionRemoved in v8Configure custom transitions by implementing renderToastItem
roleNot yet supported in v8
New in v8renderToastItemContentCustomize the toast item content area. See custom components below
New in v8renderToastItemCompletely 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.
package.json
{
  "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 signaturev8 signature
toast(message)toast.addMessage(message: InboxMessage)
New in v8toast.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.
package.json
{
  "dependencies": {
    "@trycourier/courier-react": "^8.0.28",
    "markdown-to-jsx": "^7.7.13", 
    "react": "^19.1.1",
    "react-dom": "^19.1.1"
  }
}
app.tsx
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>
        );
      }}
    />
  );
}

More Information

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.