Expo Push (Classic)

Prebuilt indie inbox component

Add a bell with an unread dot and a full-screen inbox that shows one subscriber's notifications.

Premium has no inbox

On Premium, every inbox call answers 201 with an explanation, so the SDK shows an empty inbox and an unread count of 0. Notifications sent meanwhile are kept and appear after an upgrade. See what each plan includes.

Add the bell

Put NotificationInboxBell in your header with mode="indie" and the subscriber's id:

import { NotificationInboxBell } from 'native-notify';

// Expo Router / React Navigation header:
<Stack.Screen
  name="index"
  options={{
    headerRight: () => (
      <NotificationInboxBell
        appId={yourAppId}
        appToken="yourAppToken"
        mode="indie"           // or "mass" (default)
        subId={currentUser.id} // required when mode="indie"
      />
    ),
  }}
/>

subId is the id you registered with registerIndieID. appId and appToken are optional once you set them with NativeNotify.init() or <NativeNotifyProvider>.

Indie mode

  • Each row has a delete button that calls deleteIndieNotificationInbox. The row is removed at once and restored if the request fails. Hide the button with allowDelete={false}.
  • Opening the inbox marks every notification of that subId as read, not only the fetched page, so the dot clears. To keep a read state per row, use perNotificationRead.
  • It works in Expo Go, on simulators and on the web, because read state is per subId. Mass mode needs a real device for its push token.

Per-notification read state

Needs native-notify v5.2 or later. Pass perNotificationRead to keep each row's own read state instead of marking the whole inbox read on open:

<NotificationInboxBell
  appId={yourAppId}
  appToken="yourAppToken"
  mode="indie"
  subId={currentUser.id}
  perNotificationRead
/>
  • Rows load with their real read flag, and fetching marks nothing read.
  • Unread rows show a dot. Tapping a row marks it read and refreshes the unread count.
  • The unread count comes from the server instead of being zeroed when the inbox opens.

Props

PropTypeDefaultNotes
appIdnumber | string—Your app id.
appTokenstring—Your app token.
mode'mass' | 'indie''mass'Use 'indie' for per-subscriber notifications.
subIdnumber | string—Required when mode="indie".
takenumber20Notifications fetched per page.
syncBadgebooleantrueSync the unread count to the app icon badge with Notifications.setBadgeCountAsync().
perNotificationReadbooleanfalsePer-row unread dots. See Per-notification read state.
colorsobjectlight or dark themePartial theme override. See Colors.
titlestring'Notifications'Inbox header title.
emptyTextstring"You're all caught up"Empty-state text.
allowDeletebooleantrueShow the per-row delete button (indie mode only).
onNotificationPress(notification) => void—Called when a row is tapped.
onOpen() => void—Called instead of opening the built-in inbox screen.
showCountbooleanfalseShow a numeric badge instead of a plain dot.
maxCountnumber99Badge cap. A larger count shows as 99+.
renderIcon({ unreadCount, color }) => nodebundled bell iconCustom bell icon.
iconSizenumber24Bell icon size.
iconStyleStyleProp<ImageStyle>—Style override for the bell icon.
containerStyleStyleProp<ViewStyle>—Style override for the bell container.

Colors

Colors follow the device's light or dark mode. Pass any of these keys in colors to override the theme:

<NotificationInboxBell
  appId={yourAppId}
  appToken="yourAppToken"
  mode="indie"
  subId={currentUser.id}
  colors={{ dot: '#EF4444', accent: '#2563EB' }}
/>

Keys: icon, dot, badgeText · background, headerBackground, card, border · title, text, mutedText, emptyTitle, emptyText · accent, delete.

Custom screen and hook

To use your own trigger, render <NotificationInboxScreen> with visible and onClose. It takes the props above except the bell-only ones (onOpen, showCount, maxCount, renderIcon, iconSize, iconStyle, containerStyle), plus:

PropTypeDefaultNotes
visibleboolean—Whether the inbox modal is shown.
onClose() => void—Called when the modal is dismissed.

For a fully custom UI, use the headless hook:

import { useNotificationInbox } from 'native-notify';

const {
  notifications,   // fetched inbox rows
  unreadCount,     // unread count for your own badge
  loading,         // initial load in progress
  refreshing,      // pull-to-refresh in progress
  loadingMore,     // next page in progress
  hasMore,         // more pages available
  error,           // error message, if any
  openInbox,       // loads page 1 — and marks the whole inbox read, unless perNotificationRead
  refresh,
  refreshUnread,
  loadMore,
  deleteNotification, // per-row delete (indie mode)
  perNotificationRead, // true when you built the hook with the option
  markNotificationRead, // mark ONE row read when it is opened (per-notification mode)
} = useNotificationInbox({
  appId: yourAppId,
  appToken: 'yourAppToken',
  mode: 'indie',
  subId: currentUser.id, // required when mode: 'indie'
  take: 20,
  syncBadge: true, // sync the unread count to the app icon badge
});

The hook fetches only the unread count on mount. Call openInbox() or refresh() to load rows. markNotificationRead takes the row object, not an id. For an exact "load more", use getIndieNotificationInboxPage().

Row data

Rows have the shape getIndieNotificationInbox returns:

{
  notification_id, // unique notification ID — used for deletes
  date,            // notification date
  title,           // notification title
  message,         // notification message
  pushData,        // the pushData you sent, as a JSON string — JSON.parse(row.pushData)
  read,            // this subscriber's read state — authoritative with perNotificationRead
}