skip to content

With React Native Firebase messaging, why must setBackgroundMessageHandler be registered in index.js, and what may the handler do?

level: middleimportance: should knowfreq 35%

answer

  1. before AppRegistry.registerComponent
  2. Android: Headless JS, no root mounted
  3. iOS: root component mounts too
  4. return a promise, no UI
  5. 60 s default on Android

basics

~20 s

It must be set at the top of index.js, outside any component, because background and quit-state messages run without the app's UI. The handler must return a promise, must not update UI, and may fetch data or write storage.

solid answer

~50 s

React Native Firebase calls the handler set by `setBackgroundMessageHandler` for messages that arrive in the background or quit state, so it has to exist before any UI does: its docs say to register it **outside your application logic, as early as possible**, in `index.js` before `AppRegistry.registerComponent`. On Android the handler runs as a **Headless JS** task that does not mount your root component; if no handler is set, the library only logs a warning and nothing processes the message. On iOS the app is started silently in the background and the root component **is** mounted, so effects run; the docs suggest an `isHeadless` initial prop or `getIsHeadless()` (iOS only) to render `null` then. The handler must **return a promise** when done, must **not update UI or state**, and may make network requests or write storage. On Android it has 60 seconds by default, adjustable with `messaging_android_headless_task_timeout` in `firebase.json`.

code

tsx · 16 lines
tsx
// app entry file, evaluated before the root component is registered
import { AppRegistry } from 'react-native';
import { getMessaging, setBackgroundMessageHandler } from '@react-native-firebase/messaging';
import App from './App';
import { saveIncomingChatMessage } from './chat/storage';

setBackgroundMessageHandler(getMessaging(), async (remoteMessage) => {
  await saveIncomingChatMessage(remoteMessage.data);
});

function HeadlessCheck({ isHeadless }: { isHeadless?: boolean }) {
  if (isHeadless) return null;
  return <App />;
}

AppRegistry.registerComponent('app', () => HeadlessCheck);

go deeper

for a junior

Remember that setBackgroundMessageHandler goes at the top of index.js, before registerComponent, and that it must return a promise.

for a middle

Explain Headless JS on Android, the silent background launch that mounts the root on iOS, and what the handler may and may not do.

for a senior

Guard the iOS background launch with isHeadless, keep the handler to one short storage write, respect the 60-second Android budget and design the screen to read from storage.

for a principal

Decide what background processing the product is allowed to depend on, and keep side effects such as analytics out of launches no user initiated.

## What the background handler is In a bare React Native app using `@react-native-firebase/messaging`, messages are delivered to JavaScript through two entry points: - `onMessage(messaging, listener)` — while the app is in the **foreground**; - `setBackgroundMessageHandler(messaging, handler)` — while the app is in the **background or quit**. The second one runs when there may be no UI at all, which is why its registration rules differ from ordinary listeners. ## Why it goes in index.js React Native Firebase's documentation says to call `setBackgroundMessageHandler` **outside of your application logic as early as possible**. In practice that means the top of `index.js`, before `AppRegistry.registerComponent`: 1. On **Android**, the library registers a Headless JS task named `ReactNativeFirebaseMessagingHeadlessTask`. When a background message arrives, Android starts that task, which calls whatever handler was stored. Headless JS runs **without mounting your root component**, so a handler set in `App` or an effect was never registered, and the library logs "No background message handler has been set" and resolves without doing anything. 2. On **iOS**, a data-only message arriving in the background or quit state is **delayed until the handler is registered**, which the library signals when `setBackgroundMessageHandler` is called. Registering late delays processing. ## The iOS side effect: your app mounts On iOS the mechanism differs: when a background message arrives, the device silently launches the app in the background, the handler runs, and **the root React component is mounted too**. Every effect in the tree runs, including data fetching and analytics calls that assume a user opened the app. The documented workaround: - inject an `isHeadless` initial prop from the iOS app delegate, using the library's helper for launch options, and render `null` from the root when it is true; - or, where changing launch properties is inconvenient, call `getIsHeadless(messaging)`, which is iOS-only and resolves whether the root view was launched in the background. ## What the handler may and may not do | Allowed | Not allowed | |---|---| | Network requests | Updating React state or UI | | Writing to local storage or a database | Assuming the app's screens are mounted | | Updating an unread count in storage | Running without returning a promise | | Presenting a local notification through a library | Long-running work past the timeout | The handler **must return a promise** that settles when the work is finished, so the platform can release resources. On **Android** the task has **60 seconds** by default and is then cancelled; the limit is set in milliseconds with `messaging_android_headless_task_timeout` under the `react-native` key of `firebase.json` at the project root. ## Which messages reach it - Messages with a `notification` payload: the OS has already displayed the notification, and the handler also runs. - Data-only messages: the handler runs only if they were sent with high priority on Android and `content-available` on iOS; otherwise the OS treats them as low priority and does not wake the app. ## How it differs from expo-notifications Teams that mix libraries often confuse the two background mechanisms: | Aspect | React Native Firebase | expo-notifications | |---|---|---| | Registration | `setBackgroundMessageHandler` in the entry file | `TaskManager.defineTask` + `registerTaskAsync` at module scope | | Android runner | Headless JS task | expo-task-manager | | Runs for notification messages in background | Yes | No, the OS only displays them | The shared rule is the same: register at module scope in the entry file, keep the work short and never touch UI. ## A chat example When a message for a conversation arrives while the app is backgrounded, the handler can fetch the latest messages for that conversation and write them to local storage. When the user later opens the app, the conversation screen reads from storage and is already current. The handler must not try to push the new message into a React state setter; that state may not exist. ## Common mistakes - Setting the handler inside `App` or a `useEffect`. - Forgetting that iOS mounts the root component during background launches. - Returning nothing, or never settling the promise. - Updating UI state from the handler.

  • On iOS, how can the app avoid running its full UI tree when a background message launches it?
    React Native Firebase can inject an `isHeadless` initial prop from the iOS app delegate; the root component renders `null` when it is true. If changing launch properties is awkward, `getIsHeadless(messaging)`, which is iOS-only, resolves whether the app was launched in the background, so the root can wait for it before rendering.
  • What happens on Android if the background handler takes longer than the configured timeout?
    The Headless JS task is cancelled when the limit expires, 60 seconds by default, to free device resources. Work still in flight may be cut off, so the handler should do one short job, such as saving the message, and the limit can be changed with `messaging_android_headless_task_timeout` in `firebase.json`.

saying these in an interview costs you the question

  • The background handler can be set inside the root component's useEffect
  • On Android the background handler mounts the full app UI first
  • The background handler can update React state to show the new message
  • iOS runs the background handler without mounting the root component
  • The handler does not need to return anything