You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] [Sentry: APP-KC9] Navigation crash: 'filter' of undefined in getStateForRouteNamesChange #103777
Navigator state has an undefined routes array when route names change, so .filter throws. It may relate to the react-navigation 7.x upgrade or to a screen mounting with a missing state.
Reproduction
Unknown. See the events linked from the Sentry issue.
changed the title [-][Sentry: APP-KC9] Navigation crash: 'filter' of undefined in getStateForRouteNamesChange[/-][+][$175] [Sentry: APP-KC9] Navigation crash: 'filter' of undefined in getStateForRouteNamesChange[/+]on Oct 12, 2026
(Vit's agent) @eh2077, there is no proposal on this issue yet. Can you look at the Sentry issue and tell us whether we should open it for contributors as it stands, or ask for expert contributor help because it needs deeper React Navigation knowledge?
⚠️Preliminary analysis (low confidence): treat this as a starting point, not a conclusion.
The crash is in react-navigation's stock StackRouter.getStateForRouteNamesChange. It calls state.routes.filter(...) and state.preloadedRoutes.filter(...) without null checks (StackRouter.tsx:270-276, routers 7.6.4). No App router overrides this method.
It throws only when two things happen together:
A navigator gets a stack state marked stale: false that has routeNames but no routes or preloadedRoutes.
The navigator's screen list changes at the same time.
getRehydratedState returns stale: false states unchanged (StackRouter.tsx:205-207), so the incomplete state is never repaired.
The most likely place for the screen list to change is the root stack. It renders the onboarding navigator conditionally, and part of that condition is an imperative navigation-state read during render (AuthScreens.tsx:354). Swapping between AuthScreens and PublicScreens on HybridApp also changes the root's screen names.
I couldn't confirm the root cause because I couldn't find any App code that builds a state without routes or preloadedRoutes. Sentry was also unreachable from this run. To confirm, check the Sentry events for:
The column number. It tells you whether line 270 (routes) or line 276 (preloadedRoutes) throws.
Breadcrumbs just before the crash: onboarding completion, login or logout, OldDot↔NewDot switches, validate-login or 2FA flows.
Possible defensive fix: wrap getStateForRouteNamesChange in the App's custom routers so they default routes and preloadedRoutes to [], then call the stock router. StackRouter already handles an empty routes array. Also add a Log.hmmm call when either field is missing, so the source can be found.
Investigation details
How a bad state reaches the router:useNavigationBuilder uses a stale: false state from the parent or container as-is. If its routeNames differ from the rendered screens, it goes straight to getStateForRouteNamesChange.
App code that injects state: I checked these places. None of them drops routes or preloadedRoutes, but each passes stale: false state through without rehydrating it:
Preserved navigator states in RightModalRouter, SearchFullscreenRouter and SplitRouter.
Root resets in preMountBuffer.ts, Navigation.ts and NavigationRoot.tsx (after logout).
Tab state rebuilds in GetStateForActionHandlers.ts.
Version note: the first-seen date (2026-08-24) falls in the 9.4.59–9.4.60 window. The react-navigation 7.22.1 upgrade landed after 9.5.5, so it isn't the trigger. The router code is identical in both versions.
Related:ScreenActivityWrapper (perf(navigation): add per-screen Activity behavior for covered screens, merged 2026-08-31) clears the parent's route.state when a covered screen is hidden. This makes state rebuilt from getRootState() more fragile, but it landed after the first-seen date.
No related open issues found.
Next Steps: Reply with @MelvinBot <your feedback> to refine this analysis.
Please re-state the problem that we are trying to solve in this issue.
On iOS hybrid app (and across platforms during dynamic route/screen transitions such as mounting SearchReport), the app crashes with an unhandled TypeError: Cannot read properties of undefined (reading 'filter') inside getStateForRouteNamesChange called via PlatformNavigatorImpl > useNavigationBuilder.
What is the root cause of that problem?
In React Navigation 7.x (@react-navigation/routers/src/StackRouter.tsx:270-276), getStateForRouteNamesChange is invoked by useNavigationBuilder whenever a navigator's child route definitions change while retaining an existing state:
Notice that state.routes.filter(...) and state.preloadedRoutes.filter(...) are invoked directly without null or undefined guards.
In Expensify, navigators are built using PlatformNavigatorImpl (src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx and index.native.tsx). When a navigator receives a state marked stale: false (for example, during modal transitions, screen swapping between AuthScreens and PublicScreens on HybridApp, or preserved navigator states in SearchFullscreenRouter / RightModalRouter / SplitRouter) that has routeNames but where routes is undefined or incomplete, getRehydratedState leaves stale: false states unchanged (StackRouter.tsx:205-207).
When the navigator's screen list changes at that moment (such as when conditionally mounting screens), useNavigationBuilder directly invokes router.getStateForRouteNamesChange(state, { routeNames }). Because state.routes is undefined, state.routes.filter(...) throws an uncaught TypeError: 'filter' of undefined, causing the application to crash.
What changes do you think we should make in order to solve the problem?
We should implement a clean defensive wrapper at the platform stack navigator layer that protects all navigators across both web and native platforms:
1. Add wrapRouterWithSafeRouteNamesChange in createPlatformStackNavigatorComponent
In src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx and index.native.tsx:
Define a safe router decorator:
importLogfrom'@libs/Log';functionwrapRouterWithSafeRouteNamesChange<TRouterextendsRouter<any,any>>(router: TRouter): TRouter{constoriginalGetStateForRouteNamesChange=router.getStateForRouteNamesChange;if(!originalGetStateForRouteNamesChange){returnrouter;}return{
...router,getStateForRouteNamesChange(state,options){consthasRoutes=Array.isArray(state?.routes);consthasPreloadedRoutes=Array.isArray(state?.preloadedRoutes);if(!hasRoutes||!hasPreloadedRoutes){Log.hmmm('[Navigation] getStateForRouteNamesChange received state with undefined routes/preloadedRoutes',{
hasRoutes,
hasPreloadedRoutes,routeNames: options?.routeNames,
state,});}constsafeState={
...state,routes: hasRoutes ? state.routes : [],preloadedRoutes: hasPreloadedRoutes ? state.preloadedRoutes : [],};returnoriginalGetStateForRouteNamesChange(safeState,options);},};}
2. Wrap createRouter in PlatformNavigatorImpl
In PlatformNavigatorImpl (index.tsx and index.native.tsx):
StackRouter naturally handles an empty routes array by clamping index: Math.min(state.index, routes.length - 1), safely averting the crash and allowing the rehydration pipeline to populate valid routes on the subsequent render pass, while logging diagnostic telemetry via Log.hmmm.
What alternative solutions did you explore? (Optional)
Patching @react-navigation/routers via patch-package:
We could add a patch to patches/react-navigation/ for @react-navigation/routers modifying StackRouter.getStateForRouteNamesChange to guard (state.routes ?? []).filter(...) and (state.preloadedRoutes ?? []).filter(...). While this patches upstream directly (consistent with our other react-navigation patches in patches/react-navigation/details.md), wrapping it in createPlatformStackNavigatorComponent provides immediate application-level protection with Log.hmmm diagnostics telemetry to pinpoint any callers passing incomplete states, without having to maintain additional patch rebases on future react-navigation upgrades. Either approach (or combining both) is completely viable.
Please re-state the problem that we are trying to solve in this issue.
[Sentry: APP-KC9] Unhandled TypeError: Cannot read properties of undefined (reading 'filter') occurring during navigation state route changes in getStateForRouteNamesChange when navigating to or mounting SearchReport inside PlatformNavigatorImpl.
What is the root cause of that problem?
The crash occurs due to a confluence between React Navigation 7's router implementation and Expensify's preserved navigator state lifecycle:
Unchecked array filter in @react-navigation/routers:
In @react-navigation/routers (StackRouter.tsx:270-276, v7.6.4), getStateForRouteNamesChange synchronizes state when a navigator's route definition list changes:
RightModalNavigator uses RightModalRouter (RightModalRouter.ts:15), which attempts to restore preserved state via getPreservedNavigatorState(options.parentRoute.key).
When a preserved state is restored with stale: false, getRehydratedState in StackRouter returns it untouched (StackRouter.tsx:205-207).
When screens are dynamically mounted or unmounted (e.g. SearchReport animated stacking or screen swaps), useNavigationBuilder detects that the navigator's route definitions differ from the preserved state and invokes router.getStateForRouteNamesChange(state, { routeNames }).
If the preserved state lacks a populated routes array (or if routes was cleared/omitted during parent state normalization), state.routes.filter(...) throws an unhandled TypeError: Cannot read properties of undefined (reading 'filter').
What changes do you think we should make in order to solve the problem?
We should implement a two-layer defense to guarantee both framework-level safety and application-level diagnostic monitoring:
1. Universal Guard in createPlatformStackNavigatorComponent
In src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx and src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.native.tsx:
Wrap router.getStateForRouteNamesChange in a higher-order decorator wrapRouterWithSafeRouteNamesChange. This ensures that every stack navigator across the entire app (Root, RightModal, SearchFullscreen, Split, etc.) safely normalizes missing routes and logs actionable diagnostic telemetry via Log.hmmm:
importLogfrom'@libs/Log';functionwrapRouterWithSafeRouteNamesChange<TRouterextendsRouter<any,any>>(router: TRouter,displayName: string,): TRouter{constoriginalGetStateForRouteNamesChange=router.getStateForRouteNamesChange;if(!originalGetStateForRouteNamesChange){returnrouter;}return{
...router,getStateForRouteNamesChange(state,options){consthasRoutes=Array.isArray(state?.routes);consthasPreloadedRoutes=Array.isArray(state?.preloadedRoutes);if(!hasRoutes||!hasPreloadedRoutes){Log.hmmm(`[${displayName}] getStateForRouteNamesChange received state with missing routes/preloadedRoutes`,{
displayName,
hasRoutes,
hasPreloadedRoutes,routeNames: options?.routeNames,
state,});}constsafeState={
...state,routes: hasRoutes ? state.routes : [],preloadedRoutes: hasPreloadedRoutes ? state.preloadedRoutes : [],};returnoriginalGetStateForRouteNamesChange(safeState,options);},};}
2. Defensive Upstream Patch in patches/react-navigation/
Add a patch to @react-navigation/routers (patches/react-navigation/@react-navigation+routers+7.6.4+002+safe-routes-filter-on-route-names-change.patch) to safeguard upstream execution:
3. Preserved State Sanity Guard in RightModalRouter
In src/libs/Navigation/AppNavigator/createRightModalNavigator/RightModalRouter.ts:
Ensure preservedState actually contains valid routes before using it as initial state:
What alternative solutions did you explore? (Optional)
Patching @react-navigation/routers only: While the upstream patch stops the crash, it fails to record Log.hmmm diagnostics to identify the origin of incomplete states reaching useNavigationBuilder. Implementing the decorator wrapper in createPlatformStackNavigatorComponent provides both crash prevention and telemetry.
Guarding only inside RightModalRouter: Only protects RightModalNavigator, leaving other navigators (SearchFullscreenNavigator, SplitNavigator, OnboardingModalNavigator) vulnerable if they receive an incomplete preserved state.
Automated Test Scenarios
1. Unit Test in tests/unit/PlatformStackNavigationTest.ts
importwrapRouterWithSafeRouteNamesChangefrom'@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/wrapRouterWithSafeRouteNamesChange';import{StackRouter}from'@react-navigation/native';importLogfrom'@libs/Log';describe('wrapRouterWithSafeRouteNamesChange',()=>{it('does not throw when state.routes is undefined and returns a valid state',()=>{consthmmmSpy=jest.spyOn(Log,'hmmm').mockImplementation(()=>{});constbaseRouter=StackRouter({});constwrappedRouter=wrapRouterWithSafeRouteNamesChange(baseRouter,'TestNavigator');constmalformedState={key: 'stack-1',index: 0,routeNames: ['Home','SearchReport'],routes: undefinedasany,stale: false,type: 'stack',};letresultState: any;expect(()=>{resultState=wrappedRouter.getStateForRouteNamesChange(malformedState,{routeNames: ['Home','SearchReport'],});}).not.toThrow();expect(Array.isArray(resultState.routes)).toBe(true);expect(resultState.routes).toEqual([]);expect(hmmmSpy).toHaveBeenCalledTimes(1);expect(hmmmSpy).toHaveBeenCalledWith(expect.stringContaining('[TestNavigator] getStateForRouteNamesChange received state with missing routes'),expect.any(Object),);hmmmSpy.mockRestore();});it('preserves normal route filtering when state.routes is valid',()=>{constbaseRouter=StackRouter({});constwrappedRouter=wrapRouterWithSafeRouteNamesChange(baseRouter,'TestNavigator');constvalidState={key: 'stack-1',index: 1,routeNames: ['Home','SearchReport','Settings'],routes: [{key: 'home-1',name: 'Home'},{key: 'search-1',name: 'SearchReport'},{key: 'old-1',name: 'ObsoleteRoute'},],stale: false,type: 'stack',};constresultState=wrappedRouter.getStateForRouteNamesChange(validState,{routeNames: ['Home','SearchReport'],});expect(resultState.routes).toHaveLength(2);expect(resultState.routes.map((r: any)=>r.name)).toEqual(['Home','SearchReport']);});});
2. Unit Test in tests/unit/RightModalRouterTest.ts
it('falls back to stackRouter.getInitialState when preservedState has undefined routes',()=>{// Verify getInitialState returns fresh valid initial state instead of broken preservedState});
Activity
melvin-bot commentedon Oct 12, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @eh2077 (
External)[-][Sentry: APP-KC9] Navigation crash: 'filter' of undefined in getStateForRouteNamesChange[/-][+][$175] [Sentry: APP-KC9] Navigation crash: 'filter' of undefined in getStateForRouteNamesChange[/+]melvin-bot commentedon Oct 12, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022109653565102124888
mountiny commentedon Oct 12, 2026
(Vit's agent) @eh2077, there is no proposal on this issue yet. Can you look at the Sentry issue and tell us whether we should open it for contributors as it stands, or ask for expert contributor help because it needs deeper React Navigation knowledge?
MelvinBot commentedon Oct 12, 2026
The crash is in react-navigation's stock
StackRouter.getStateForRouteNamesChange. It callsstate.routes.filter(...)andstate.preloadedRoutes.filter(...)without null checks (StackRouter.tsx:270-276, routers 7.6.4). No App router overrides this method.It throws only when two things happen together:
stale: falsethat hasrouteNamesbut noroutesorpreloadedRoutes.getRehydratedStatereturnsstale: falsestates unchanged (StackRouter.tsx:205-207), so the incomplete state is never repaired.The most likely place for the screen list to change is the root stack. It renders the onboarding navigator conditionally, and part of that condition is an imperative navigation-state read during render (AuthScreens.tsx:354). Swapping between AuthScreens and PublicScreens on HybridApp also changes the root's screen names.
I couldn't confirm the root cause because I couldn't find any App code that builds a state without
routesorpreloadedRoutes. Sentry was also unreachable from this run. To confirm, check the Sentry events for:routes) or line 276 (preloadedRoutes) throws.Possible defensive fix: wrap
getStateForRouteNamesChangein the App's custom routers so they defaultroutesandpreloadedRoutesto[], then call the stock router. StackRouter already handles an emptyroutesarray. Also add aLog.hmmmcall when either field is missing, so the source can be found.Investigation details
useNavigationBuilderuses astale: falsestate from the parent or container as-is. If itsrouteNamesdiffer from the rendered screens, it goes straight togetStateForRouteNamesChange.routesorpreloadedRoutes, but each passesstale: falsestate through without rehydrating it:RightModalRouter,SearchFullscreenRouterandSplitRouter.preMountBuffer.ts,Navigation.tsandNavigationRoot.tsx(after logout).GetStateForActionHandlers.ts.ScreenActivityWrapper(perf(navigation): add per-screen Activity behavior for covered screens, merged 2026-08-31) clears the parent'sroute.statewhen a covered screen is hidden. This makes state rebuilt fromgetRootState()more fragile, but it landed after the first-seen date.Next Steps: Reply with
@MelvinBot <your feedback>to refine this analysis.view run
ashusatyarthi-wq commentedon Oct 12, 2026
Contributor details
Your Expensify account email: ashusatyarthi@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~010e53044b5828fe60
Proposal
Please re-state the problem that we are trying to solve in this issue.
On iOS hybrid app (and across platforms during dynamic route/screen transitions such as mounting SearchReport), the app crashes with an unhandled TypeError:
Cannot read properties of undefined (reading 'filter')insidegetStateForRouteNamesChangecalled viaPlatformNavigatorImpl>useNavigationBuilder.What is the root cause of that problem?
In React Navigation 7.x (
@react-navigation/routers/src/StackRouter.tsx:270-276),getStateForRouteNamesChangeis invoked byuseNavigationBuilderwhenever a navigator's child route definitions change while retaining an existing state:Notice that
state.routes.filter(...)andstate.preloadedRoutes.filter(...)are invoked directly without null or undefined guards.In Expensify, navigators are built using
PlatformNavigatorImpl(src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsxandindex.native.tsx). When a navigator receives a state markedstale: false(for example, during modal transitions, screen swapping betweenAuthScreensandPublicScreenson HybridApp, or preserved navigator states inSearchFullscreenRouter/RightModalRouter/SplitRouter) that hasrouteNamesbut whereroutesis undefined or incomplete,getRehydratedStateleavesstale: falsestates unchanged (StackRouter.tsx:205-207).When the navigator's screen list changes at that moment (such as when conditionally mounting screens),
useNavigationBuilderdirectly invokesrouter.getStateForRouteNamesChange(state, { routeNames }). Becausestate.routesis undefined,state.routes.filter(...)throws an uncaught TypeError:'filter' of undefined, causing the application to crash.What changes do you think we should make in order to solve the problem?
We should implement a clean defensive wrapper at the platform stack navigator layer that protects all navigators across both web and native platforms:
1. Add
wrapRouterWithSafeRouteNamesChangeincreatePlatformStackNavigatorComponentIn
src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsxandindex.native.tsx:Define a safe router decorator:
2. Wrap
createRouterinPlatformNavigatorImplIn
PlatformNavigatorImpl(index.tsxandindex.native.tsx):StackRouter naturally handles an empty
routesarray by clampingindex: Math.min(state.index, routes.length - 1), safely averting the crash and allowing the rehydration pipeline to populate valid routes on the subsequent render pass, while logging diagnostic telemetry viaLog.hmmm.What alternative solutions did you explore? (Optional)
@react-navigation/routersvia patch-package:We could add a patch to
patches/react-navigation/for@react-navigation/routersmodifyingStackRouter.getStateForRouteNamesChangeto guard(state.routes ?? []).filter(...)and(state.preloadedRoutes ?? []).filter(...). While this patches upstream directly (consistent with our other react-navigation patches inpatches/react-navigation/details.md), wrapping it increatePlatformStackNavigatorComponentprovides immediate application-level protection withLog.hmmmdiagnostics telemetry to pinpoint any callers passing incomplete states, without having to maintain additional patch rebases on future react-navigation upgrades. Either approach (or combining both) is completely viable.melvin-bot commentedon Oct 12, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
janiya2 commentedon Oct 12, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
[Sentry: APP-KC9] Unhandled
TypeError: Cannot read properties of undefined (reading 'filter')occurring during navigation state route changes ingetStateForRouteNamesChangewhen navigating to or mountingSearchReportinsidePlatformNavigatorImpl.What is the root cause of that problem?
The crash occurs due to a confluence between React Navigation 7's router implementation and Expensify's preserved navigator state lifecycle:
Unchecked array filter in
@react-navigation/routers:In
@react-navigation/routers(StackRouter.tsx:270-276, v7.6.4),getStateForRouteNamesChangesynchronizes state when a navigator's route definition list changes:Notice that while
preloadedRouteshas a fallback guard(state.preloadedRoutes ?? []),state.routeshas no guard at all.Why
SearchReporttriggers this in production:The Sentry issue explicitly identifies
SearchReporton iOS hybrid app. In Expensify:SearchReportresides insideRightModalNavigator(SCREENS.RIGHT_MODAL.SEARCH_REPORT, RightModalNavigator.tsx:516).RightModalNavigatorusesRightModalRouter(RightModalRouter.ts:15), which attempts to restore preserved state viagetPreservedNavigatorState(options.parentRoute.key).stale: false,getRehydratedStateinStackRouterreturns it untouched (StackRouter.tsx:205-207).SearchReportanimated stacking or screen swaps),useNavigationBuilderdetects that the navigator's route definitions differ from the preserved state and invokesrouter.getStateForRouteNamesChange(state, { routeNames }).routesarray (or ifrouteswas cleared/omitted during parent state normalization),state.routes.filter(...)throws an unhandledTypeError: Cannot read properties of undefined (reading 'filter').What changes do you think we should make in order to solve the problem?
We should implement a two-layer defense to guarantee both framework-level safety and application-level diagnostic monitoring:
1. Universal Guard in
createPlatformStackNavigatorComponentIn
src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsxandsrc/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.native.tsx:Wrap
router.getStateForRouteNamesChangein a higher-order decoratorwrapRouterWithSafeRouteNamesChange. This ensures that every stack navigator across the entire app (Root, RightModal, SearchFullscreen, Split, etc.) safely normalizes missingroutesand logs actionable diagnostic telemetry viaLog.hmmm:In
PlatformNavigatorImpl:2. Defensive Upstream Patch in
patches/react-navigation/Add a patch to
@react-navigation/routers(patches/react-navigation/@react-navigation+routers+7.6.4+002+safe-routes-filter-on-route-names-change.patch) to safeguard upstream execution:3. Preserved State Sanity Guard in
RightModalRouterIn
src/libs/Navigation/AppNavigator/createRightModalNavigator/RightModalRouter.ts:Ensure
preservedStateactually contains valid routes before using it as initial state:getInitialState({routeNames, routeParamList, routeGetIdList}: RouterConfigOptions) { const preservedState = getPreservedNavigatorState(options.parentRoute.key); - return preservedState ?? stackRouter.getInitialState({routeNames, routeParamList, routeGetIdList}); + if (preservedState && Array.isArray(preservedState.routes) && preservedState.routes.length > 0) { + return preservedState; + } + return stackRouter.getInitialState({routeNames, routeParamList, routeGetIdList}); },What alternative solutions did you explore? (Optional)
@react-navigation/routersonly: While the upstream patch stops the crash, it fails to recordLog.hmmmdiagnostics to identify the origin of incomplete states reachinguseNavigationBuilder. Implementing the decorator wrapper increatePlatformStackNavigatorComponentprovides both crash prevention and telemetry.RightModalRouter: Only protectsRightModalNavigator, leaving other navigators (SearchFullscreenNavigator,SplitNavigator,OnboardingModalNavigator) vulnerable if they receive an incomplete preserved state.Automated Test Scenarios
1. Unit Test in
tests/unit/PlatformStackNavigationTest.ts2. Unit Test in
tests/unit/RightModalRouterTest.tsContributor Details
eh2077 commentedon Oct 14, 2026
start checking the issue