App State and Hooks
Screen lifecycle, app foreground/background, system theme, and hook patterns that matter on mobile.
Search across all documentation pages
Screen lifecycle, app foreground/background, system theme, and hook patterns that matter on mobile.
Local component state works the same as web React - ideal for form fields and ephemeral UI flags.
const [open, setOpen] = useState(false);Subscribe on mount and always clean up. Mobile apps background frequently - avoid leaked timers and listeners.
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);AppState tells you active vs background. Pause video, sockets, or polling when inactive.
import { AppState } from "react-native";
useEffect(() => {
const sub = AppState.addEventListener("change", (next) => {
if (next === "active") refresh();
});
return () => sub.remove();
}, []);Run an effect when a screen gains focus in the navigation tree (not only on mount).
import { useFocusEffect } from "expo-router";
import { useCallback } from "react";
useFocusEffect(
useCallback(() => {
refresh();
return () => abort();
}, []),
);React to orientation and split-view size changes without manual event wiring.
import { useWindowDimensions } from "react-native";
const { width, height } = useWindowDimensions();Read the system light/dark preference for theming.
import { useColorScheme } from "react-native";
const scheme = useColorScheme(); // "light" | "dark" | null
const bg = scheme === "dark" ? "#0f172a" : "#ffffff";Listen for theme changes if you need side effects beyond re-render.
import { Appearance } from "react-native";
useEffect(() => {
const sub = Appearance.addChangeListener(({ colorScheme }) => {
analytics.theme(colorScheme);
});
return () => sub.remove();
}, []);Defer expensive work until animations and gestures finish for smoother navigation transitions.
import { InteractionManager } from "react-native";
useEffect(() => {
const task = InteractionManager.runAfterInteractions(() => {
warmCache();
});
return () => task.cancel();
}, []);Store interval/timeout ids in refs so handlers always clear the latest timer.
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
function schedule(fn: () => void) {
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(fn, 300);
}Intercept the hardware back button (and some gesture backs depending on setup).
import { BackHandler } from "react-native";
useEffect(() => {
const sub = BackHandler.addEventListener("hardwareBackPress", () => {
if (dirty) {
confirmDiscard();
return true; // handled
}
return false;
});
return () => sub.remove();
}, [dirty]);Prefer useWindowDimensions for UI; use the event API for imperative work on size change.
import { Dimensions } from "react-native";
useEffect(() => {
const sub = Dimensions.addEventListener("change", ({ window }) => {
layoutEngine.relayout(window);
});
return () => sub.remove();
}, []);Small shared hooks reduce boilerplate for boolean UI state.
function useToggle(init = false) {
const [on, setOn] = useState(init);
const toggle = useCallback(() => setOn((v) => !v), []);
return [on, toggle, setOn] as const;
}Cancel in-flight requests when leaving a screen so setState does not run after unmount.
useEffect(() => {
const ac = new AbortController();
fetch(url, { signal: ac.signal })
.then((r) => r.json())
.then(setData)
.catch((e) => {
if (e.name !== "AbortError") setError(e);
});
return () => ac.abort();
}, [url]);Stabilize callbacks passed into memoized list rows or navigation options.
const onPressItem = useCallback((id: string) => {
router.push(`/items/${id}`);
}, []);Memoize filtered/sorted lists when the derivation is non-trivial and inputs change often.
const visible = useMemo(
() => items.filter((i) => i.title.includes(query)),
[items, query],
);Stack versions: React 19.2.3 · React Native 0.86.0 · Expo SDK 57 · Expo Router
Reviewed by Chris St. John·Last updated Jul 18, 2026