# Navigation state persists across app backgrounding

- **Pattern:** `ab-001869` (`mobile-navigation-linking.state-management.persist-on-background`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001869
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001869)

## Why it matters

Mobile OSes aggressively kill backgrounded apps to reclaim memory. Without state persistence, a user who navigated three screens deep, switched to Maps for directions, and came back lands on a cold splash screen with their place lost — breaking the session and dumping any in-progress form input. This is especially punishing on iOS where low-memory terminations are silent, and it corrupts any analytics funnel that assumes continuous sessions.

## Severity rationale

Medium because state loss is only visible after background kills but severely degrades the session-continuity experience.

## Remediation

Persist navigation state to AsyncStorage on change and restore it on mount before rendering `NavigationContainer`. Put this in `src/navigation/RootNavigator.tsx`:

```tsx
import AsyncStorage from '@react-native-async-storage/async-storage'

const [initialState, setInitialState] = useState()
const [isReady, setIsReady] = useState(false)

useEffect(() => {
  AsyncStorage.getItem('NAV_STATE').then((s) => {
    if (s) setInitialState(JSON.parse(s))
    setIsReady(true)
  })
}, [])

if (!isReady) return null

return (
  <NavigationContainer
    initialState={initialState}
    onStateChange={(s) => AsyncStorage.setItem('NAV_STATE', JSON.stringify(s))}
  >
    <RootNavigator />
  </NavigationContainer>
)
```

## Detection

- **ID:** `persist-on-background`
- **Severity:** `medium`
- **What to look for:** Test the app by navigating to a nested screen, backgrounding the app (pressing home), and returning. Check whether the app returns to the same screen. Look for `NavigationContainer.onStateChange` persisting state to storage and restoration logic in the app's initialization.
- **Pass criteria:** Count all navigation state persistence mechanisms (onStateChange + AsyncStorage, or equivalent). When the app is backgrounded and reopened, the user is returned to the same screen they were on, with the same navigation stack preserved. At least 1 persistence mechanism must exist using `onStateChange` callback with a storage write.
- **Fail criteria:** Backgrounding the app resets navigation to the home screen or splash screen. Users lose their place in the app. No more than 0 backgrounding scenarios should result in navigation state loss.
- **Skip (N/A) when:** The app does not require navigation state persistence (fewer than 3 screens).
- **Detail on fail:** `"App resets to home screen when backgrounded and reopened"` or `"Navigation state is not persisted between sessions"`
- **Remediation:** Persist and restore navigation state across app backgrounding:

  ```tsx
  import AsyncStorage from '@react-native-async-storage/async-storage'

  const RootNavigator = () => {
    const [isReady, setIsReady] = useState(false)
    const [initialState, setInitialState] = useState()

    useEffect(() => {
      const restoreState = async () => {
        try {
          const savedState = await AsyncStorage.getItem('navigationState')
          if (savedState) {
            setInitialState(JSON.parse(savedState))
          }
        } finally {
          setIsReady(true)
        }
      }

      restoreState()
    }, [])

    if (!isReady) return null

    return (
      <NavigationContainer
        initialState={initialState}
        onStateChange={state => {
          AsyncStorage.setItem('navigationState', JSON.stringify(state))
        }}
      >
        {/* navigators */}
      </NavigationContainer>
    )
  }
  ```

Taxons: user-experience, data-integrity

HTML version: https://auditbuffet.com/patterns/ab-001869
