In React Native, what do the AppState values active, background and inactive mean, and how do you subscribe to changes?
answer
- three values, one iOS-only
- inactive: iOS transitions and interruptions
- currentState for a synchronous read
- addEventListener('change') returns a subscription
- subscription.remove() in the effect cleanup
basics
~10 sReact 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 linesimport { 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
Name the three values, say that inactive is iOS-only, and show the useEffect that subscribes with addEventListener and calls remove() in its cleanup.
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.
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.
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.