With expo-notifications, why does a push not appear while the React Native app is open, and what does setNotificationHandler change?
answer
- foreground: the app decides
- no handler means not shown
- answer within 3 seconds
- banner, list, sound, badge
- shouldShowAlert is deprecated
basics
~10 sIn the foreground the app, not the operating system, decides how a notification is presented, and with no handler expo-notifications shows nothing. setNotificationHandler returns a behavior per notification: banner, list, sound and badge.
solid answer
~40 sWhen a notification arrives while the app is in the foreground, expo-notifications asks the app what to do with it. The default, when no handler is set or it does not answer in time, is **not to show it**. `Notifications.setNotificationHandler({ handleNotification })` registers the callback: it receives the notification and must resolve a `NotificationBehavior` within **3 seconds**, with `shouldShowBanner`, `shouldShowList`, `shouldPlaySound` and `shouldSetBadge` (the badge flag is iOS-only). `shouldShowAlert` is deprecated in favour of the banner and list flags. In a chat app the handler is where you suppress the banner for the conversation already on screen and show it for any other, while `addNotificationReceivedListener` updates in-app UI such as the unread counter.
code
typescript · 19 linesimport * as Notifications from 'expo-notifications';
let openConversationId: string | null = null;
export const setOpenConversation = (id: string | null) => {
openConversationId = id;
};
Notifications.setNotificationHandler({
handleNotification: async (notification) => {
const conversationId = notification.request.content.data?.conversationId;
const alreadyReading = conversationId === openConversationId;
return {
shouldShowBanner: !alreadyReading,
shouldShowList: true,
shouldPlaySound: !alreadyReading,
shouldSetBadge: false,
};
},
});go deeper
Remember that in the foreground the app decides, that expo-notifications shows nothing without a handler, and the four behavior flags: banner, list, sound, badge.
Explain the handler's contract: a behavior within 3 seconds or the notification is dropped, module-scope registration, and how it differs from the received listener.
Show production judgment: per-conversation suppression, keeping the decision in memory to meet the deadline, and the Android quirk where disabling sound also removes the heads-up banner.
Treat foreground presentation as product policy owned in one place, so every feature that sends notifications gets consistent in-app behavior instead of ad-hoc handlers.
## Who presents a notification, by app state A push that reaches a React Native app is presented by different parties depending on the app's state: - **Background or killed**: for a notification message with a title and body, the **operating system** shows it in the tray or as a banner; no JavaScript needs to run. - **Foreground**: the platform hands the notification to the **app**, which decides whether and how to present it. expo-notifications makes that decision a JavaScript callback. Its documented default is strict: **when no handler is set, or the handler does not respond in time, the notification is not shown**. That is why a team testing with the app open often concludes that "push is broken" when delivery is fine. ## setNotificationHandler `Notifications.setNotificationHandler(handler)` takes an object with: | Member | Required | Purpose | |---|---|---| | `handleNotification(notification)` | Yes | Resolves a `NotificationBehavior` for this notification | | `handleSuccess(id)` | No | Called after the behavior was applied | | `handleError(id, error)` | No | Called when the callback throws or times out | The behavior object has these fields: - `shouldShowBanner` — present it as a banner over the app; - `shouldShowList` — keep it in the notification list; - `shouldPlaySound` — play its sound; on Android, `false` also stops the heads-up drop-down from showing, whatever the priority; - `shouldSetBadge` — update the app icon badge, iOS only; - `priority` — an optional Android priority. `shouldShowAlert` still type-checks but is **deprecated**: expo-notifications logs a warning and asks for `shouldShowBanner` and/or `shouldShowList` instead. On iOS the flags map to the system's presentation options for foreground notifications. Passing `null` clears the handler, which returns the app to the default of showing nothing. ## The 3-second deadline `handleNotification` must resolve **within 3 seconds**, otherwise the notification is discarded and `handleError` receives a timeout error. Consequences: 1. Keep the callback cheap: read in-memory state, do not fetch from the network. 2. Do not wait on a slow storage read to decide; keep the needed state (such as the open conversation id) in memory. 3. Remember that a discarded notification is not retried later. ## Where to call it Call `setNotificationHandler` once, at **module scope** in a file that loads early (the app entry or root layout), not inside a screen. A handler set inside a screen exists only while that screen is mounted, so notifications arriving on other screens fall back to the default and vanish. ## A chat example The classic interview follow-up is the chat case: a message for the conversation the user is currently reading should not pop a banner, while a message for any other conversation should. The handler compares the notification's data with the conversation on screen and answers accordingly. The screen updates its own id in a module-level variable or store when it gains and loses focus. Separately, `Notifications.addNotificationReceivedListener` fires for notifications received while the app is running in the foreground. It does not decide presentation; it is for side effects such as incrementing an unread counter or refreshing the message list. ## Testing the handler Foreground behavior is easy to get wrong because developers usually test with the app open and the handler already set. A short manual matrix catches most defects: 1. App open on the conversation named in the push: no banner, no sound, message appears in the list. 2. App open on another screen: banner and sound. 3. App in the background: the OS shows the notification; the handler is not involved. 4. Handler deliberately slowed past 3 seconds in a debug build: the notification disappears and `handleError` fires. Running the matrix on both platforms matters, because iOS maps the flags to its presentation options while Android maps them to its own display rules, including the sound quirk above. ## Common mistakes - Expecting the OS to show foreground notifications automatically, as it does in the background. - Setting the handler inside one screen's effect. - Doing network work inside `handleNotification` and hitting the 3-second timeout. - Still returning only `shouldShowAlert` and relying on a deprecated field. - Confusing the handler (presentation) with the received listener (side effects).
- What is the difference between setNotificationHandler and addNotificationReceivedListener in expo-notifications?The handler decides presentation: it must return a `NotificationBehavior` within 3 seconds, and without it foreground notifications are not shown. The received listener only observes: it fires for notifications received while the app is in the foreground and is used for side effects such as refreshing the chat list or incrementing an unread counter. It cannot change how the notification is shown.
- Why can setting shouldPlaySound to false on Android hide the banner as well?expo-notifications documents that on Android `shouldPlaySound: false` stops the heads-up drop-down alert from showing regardless of priority, and it overrides channel-specific sounds. So a handler meant to show a silent banner on Android may show no banner at all; the notification still lands in the list if `shouldShowList` is true.
saying these in an interview costs you the question
- The operating system always shows notifications, even when the app is open
- Without a handler, expo-notifications shows foreground notifications by default
- handleNotification can take as long as it needs, for example to fetch data
- shouldShowAlert is the current way to show a foreground banner
- addNotificationReceivedListener decides whether the notification is displayed