Fix React Navigation v6 State Persistence Issues
When React Navigation v6 resets to the root screen on restart, the persistence layer is likely misconfigured. This guide walks through common causes, a step‑by‑step diagnostic checklist, targeted fixes, and escalation paths to help you restore navigation state reliably.
04 Sept 2025, 19:10 UTC

Problem Overview
When an app built with React Navigation v6 restarts, the navigator often resets to the root screen instead of restoring the last visited route. This symptom usually means the persistence layer is not working correctly. The goal of this guide is to help you pinpoint the exact cause and apply the right fix, using a structured diagnostic flow.
Common Causes
| Cause | Description |
|---|---|
Missing or wrong persistenceKey | State is never written or read because the key is absent or mismatched. |
| Improper AsyncStorage usage | Using the deprecated react-native storage or a sync API blocks the persistence promise. |
| State JSON mismatch | Navigator structure changed (new screens, renamed routes) without migration. |
| Hidden persistence errors | Errors are swallowed by the library, leaving no console hints. |
| Deep linking vs. persistence conflict | Both enabled without precedence logic, producing contradictory initial states. |
Diagnostic Checklist
- Verify persistence key consistency
- Open
NavigationContainerand confirmpersistenceKeyis set and matches the key used inAsyncStorage. - Run
adb shell run-as com.your.app ls /data/data/com.your.app/files(Android) or inspect Xcode’s console (iOS) to see the stored JSON.
- Open
- Check storage implementation
- Ensure you import from
@react-native-async-storage/async-storage. - Confirm the library is linked (autolinked in RN 0.60+, or run
npx pod-installon iOS).
- Ensure you import from
- Inspect the stored state JSON
- It should be a valid JSON object with keys like
indexandroutes. - Routes should match the current navigator’s route names.
- It should be a valid JSON object with keys like
- Run the app with logs
npx react-native log-android # or npx react-native log-ios- Look for
Restoring navigation stateandFailed to restore navigation statemessages. - Check for warnings about non‑serializable params.
- Look for
- Test persistence toggle
- Wrap
NavigationContainerwithenablePersistence={true}and restart the app. - Assert that the last route is shown after restart.
- Wrap
Fixes Tied to Findings
1. Persistence Key Mismatch
Set a unique, consistent key and use it in both the container and storage.
import AsyncStorage from '@react-native-async-storage/async-storage';
const PERSISTENCE_KEY = 'NAVIGATION_STATE_V1';
export default function App() {
const [isReady, setIsReady] = React.useState(false);
const [initialState, setInitialState] = React.useState();
React.useEffect(() => {
const restoreState = async () => {
try {
const savedState = await AsyncStorage.getItem(PERSISTENCE_KEY);
if (savedState) setInitialState(JSON.parse(savedState));
} finally {
setIsReady(true);
}
};
restoreState();
}, []);
if (!isReady) return null;
return (
AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state))}
>
{/* ... navigator screens ... */}
);
}
Risk: If PERSISTENCE_KEY changes after data is stored, the old state will never be loaded.
2. Using the Wrong Storage API
Replace any sync AsyncStorage calls with the async API and ensure the library is installed:
npm install @react-native-async-storage/async-storage
# or
yarn add @react-native-async-storage/async-storage
Do not use react-native built‑in storage; it is deprecated and can throw runtime errors.
3. State JSON Mismatch
When screens are added or renamed, migrate the persisted state. A simple approach is to clear the key on navigation changes that alter the structure:
const handleStateChange = state => {
if (state.routes.some(r => r.name === 'LegacyScreen')) {
AsyncStorage.removeItem(PERSISTENCE_KEY);
}
AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state));
};
Alternatively, implement a versioning system inside the stored JSON and migrate automatically.
4. Silent Persistence Errors
Wrap persistence calls in try/catch and log errors to the console or a remote logger:
try {
await AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state));
} catch (e) {
console.error('Navigation persistence failed', e);
}
Check for Non‑serializable values were found in the navigation state warnings and refactor params to primitives.
5. Deep Linking vs. Persistence Conflict
Define a clear precedence. For example, if a deep link is present, ignore persisted state:
const linking = {
prefixes: ['myapp://'],
config: {/* ... */},
};
// In the container
Loading...}
initialState={initialState}
onStateChange={handleStateChange}
>
Use linking.prefixes to detect incoming URLs and conditionally skip state restoration.
Escalation Criteria
- If after applying all fixes the navigator still resets, the issue may be device‑specific (e.g., storage quota reached). Check the stored JSON size.
- On iOS, verify that
App Groupsare not interfering withNSUserDefaultsused byAsyncStorage. - If deep linking is used extensively, consider isolating deep link handling in a separate navigator to avoid cross‑talk with persisted routes.
- When non‑serializable params are unavoidable, serialize them manually before storing and deserialize on restore.
Practical Verification
After making changes, perform the following steps:
- Run the app, navigate to a deep screen, and close the app.
- Restart the app and confirm the same screen appears.
- Check the console for
Restoring navigation stateand no error messages. - Inspect the stored JSON to ensure it matches the current navigator structure.
If any step fails, revisit the corresponding diagnostic check.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.