skip to content

Foreground & Background

AppState reports whether the app is active, in the background or (on iOS) inactive, and apps pause timers and sockets on each change. Interviewers ask what the OS does to a backgrounded JS runtime.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

3

In React Native, what do the AppState values active, background and inactive mean, and how do you subscribe to changes?

level: juniorimportance: must knowfreq 62%

answer

  1. three values, one iOS-only
  2. inactive: iOS transitions and interruptions
  3. currentState for a synchronous read
  4. addEventListener('change') returns a subscription
  5. subscription.remove() in the effect cleanup

basics

~10 s

React Native's AppState reports active (foreground), background, or, on iOS only, inactive during transitions. Read AppState.currentState, subscribe with AppState.addEventListener('change', handler), and call remove() on the returned subscription.

solid answer

~40 s

`active` means the app is in the foreground; `background` means the user is in another app or on the home screen, or, on Android, another `Activity` covers yours; `inactive` is iOS-only and marks transitions and interruptions such as the app switcher, Notification Center or an incoming call. `AppState.currentState` gives the latest value synchronously, and `AppState.addEventListener('change', handler)` calls the handler with each new value. The call returns an `EventSubscription`, and you call `subscription.remove()` in the `useEffect` cleanup, because current React Native has no `AppState.removeEventListener`. To detect a return to the foreground, keep the previous value in a ref and act when it moves from `inactive` or `background` to `active`.

code

tsx · 20 lines
tsx
import { useEffect, useRef, useState } from 'react';
import { AppState, type AppStateStatus } from 'react-native';

export function useAppState(onForeground?: () => void): AppStateStatus {
  const previous = useRef<AppStateStatus>(AppState.currentState);
  const [state, setState] = useState<AppStateStatus>(AppState.currentState);

  useEffect(() => {
    const subscription = AppState.addEventListener('change', next => {
      if (/inactive|background/.test(previous.current) && next === 'active') {
        onForeground?.();
      }
      previous.current = next;
      setState(next);
    });
    return () => subscription.remove();
  }, [onForeground]);

  return state;
}

go deeper

for a junior

Name the three values, say that inactive is iOS-only, and show the useEffect that subscribes with addEventListener and calls remove() in its cleanup.

for a middle

Explain how to detect a return to the foreground with a previous-value ref, and why the iOS sequences differ from Android's: inactive then background on leaving, no inactive step on Android.

for a senior

Show that you design lifecycle handlers per platform: Android reports background when another Activity covers the app, iOS reports inactive for brief interruptions, and handlers must tolerate both.

for a principal

Argue for one shared app-lifecycle hook or service instead of scattered listeners, so pause and resume rules are decided once and reviewed per platform.

## What AppState reports `AppState` is a core React Native module (imported from `react-native`) that tells JavaScript whether the app is in the foreground and notifies it when that changes. It is backed by a native module on each platform: on iOS it listens to the `UIApplication` lifecycle notifications, and on Android it listens to the host Activity's resume and pause callbacks. Its value is a short string, and three of them matter in everyday code: | Value | Platforms | When you see it | |---|---|---| | `active` | iOS and Android | The app is in the foreground and the user can interact with it | | `background` | iOS and Android | The user is in another app or on the home screen; on Android also when another `Activity` covers yours, including system activities such as autofill credential pickers | | `inactive` | iOS only | A transition or interruption: moving between foreground and background, the multitasking view, Notification Center, an incoming call | The TypeScript type `AppStateStatus` also lists `unknown` and `extension`, both iOS-only edge cases (a value before the state is known, and code running inside an app extension). Android's native module only ever produces `active` or `background`, so code that waits for `inactive` on Android waits forever. ## Reading the current value `AppState.currentState` is a synchronous read of the latest known value. React Native keeps it current by registering its own internal listener, so you can read it at any time, for example to initialise a `useRef` or `useState`. Under the New Architecture, the only architecture since React Native 0.82, the initial value comes from the native module's constants when `AppState` is first loaded, so it holds the launch state. The documentation notes that under the old legacy architecture it could be `null` until the native side answered asynchronously, which is why older code guards against `null`. ## Subscribing and unsubscribing `AppState.addEventListener(type, handler)` registers a handler and **returns an `EventSubscription`**. The events are: - `change`: the handler receives the new `AppStateStatus` string; - `memoryWarning`: iOS only, fired when the system issues a memory warning; - `focus` and `blur`: Android only, fired when the app's window gains or loses focus (for example when the notification drawer is pulled down) while the state stays `active`. To stop listening you call `remove()` on the returned subscription. Current React Native has no `AppState.removeEventListener`; `AppState` also stopped inheriting from `NativeEventEmitter` years ago, so `addListener` and `removeAllListeners` are not on it either. In a function component the pattern is a `useEffect` that subscribes and returns a cleanup: ```tsx useEffect(() => { const subscription = AppState.addEventListener('change', next => { console.log('AppState is now', next); }); return () => subscription.remove(); }, []); ``` Forgetting the cleanup leaves the handler registered after the component unmounts, so it keeps running against stale state. ## Detecting a return to the foreground A common task is "do something when the user comes back": refresh a timestamp, re-check a permission, resume a paused feature. The `change` event gives only the new value, so you keep the previous one yourself, usually in a `useRef` initialised from `AppState.currentState`, and act when the previous value was `inactive` or `background` and the new one is `active`. Two platform details shape this: 1. On iOS, leaving the app normally produces `inactive` and then `background`; coming back produces `active` directly. 2. On iOS, a brief interruption such as Notification Center produces `inactive` and then `active` again without ever reaching `background`. 3. On Android, leaving produces `background` and coming back produces `active`; there is no `inactive` step. Matching the previous value against both `inactive` and `background` handles all three sequences with one check. ## Common mistakes - Treating `inactive` as cross-platform and writing Android logic that depends on it. - Calling a `removeEventListener` method that current React Native does not have. - Reading `currentState` once on mount and never subscribing, so the value goes stale. - Assuming `active` says which screen is focused; `AppState` knows about the whole app, not about navigation. - Re-creating the subscription on every render by passing an unstable callback in the effect's dependency list. `AppState` observes the lifecycle; it does not change it. Registering a listener neither keeps the app running in the background nor delays suspension.

  • Besides 'change', which events can a React Native app subscribe to on AppState, and on which platforms?
    `memoryWarning` fires on iOS when the system issues a memory warning, a cue to drop caches. `focus` and `blur` are Android-only and follow the window's focus: pulling down the notification drawer fires `blur` while the state stays `active`, and closing it fires `focus`. All of them use the same `addEventListener` call and return a subscription you `remove()`.
  • Why does React Native code often guard AppState.currentState against null, and is that still needed in 0.87?
    Under the legacy architecture the initial value was fetched asynchronously from native code, so `currentState` could be `null` right after launch. In the New Architecture, the only architecture since 0.82, it is initialised from the native module's constants when `AppState` loads. A guard does no harm, but it is legacy caution, not a requirement.

saying these in an interview costs you the question

  • AppState reports inactive on Android as well as on iOS.
  • You unsubscribe with AppState.removeEventListener('change', handler).
  • AppState.currentState is always null until the first change event fires.
  • AppState tells you which navigation screen is currently focused.
  • Adding an AppState listener keeps the app running in the background.
open as a page

In React Native, what happens to the JavaScript runtime, its timers and your in-memory state after the app moves to the background?

level: middleimportance: should knowfreq 45%

basics

~20 s

The JavaScript runtime stays in memory but stops getting time: React Native pauses its timers, iOS soon suspends the process, and either OS may later kill it without any JavaScript event. Save state when AppState reports background.

open as a page

A React Native video call pauses the camera whenever AppState leaves 'active'; why does video drop out on iOS, and which transitions should pause and resume it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

On iOS, AppState reports inactive for brief interruptions such as Notification Center, the app switcher or a permission prompt, so a check for anything but active tears video down needlessly. Pause on background, resume on active, and guard both.

open as a page