Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: prevent navigation state updates after state cleanup (#9688)
Problem: When using nested navigators, unmounts cause race cleanup races. Imagine following hierarchy (from tree root downwards, parent to children): TabNavigator (1) [renders useNavigationBuilder] SceneView (from TabNavigator) StackNavigators (N) [each renders useNavigationBuilder] SceneView (from StackNavigator) Now lets test following flow: 1. Mount above navigators with given navigation params (e.g. navigation for unauthenticated users) 2. Unmount all navigators (e.g. during login process) 3. Mount above navigation with different navigation params than in 1) (e.g. navigation for authenticated users) What you'll observe, there will be old navigation params preserved in 3) coming from 1). Source of problem: BaseNavigationContainer holds global navigation state, exposes API to modify it via NavigationStateContext. When useNavigationBuilder unmounts, it attempts to clear navigation state. (see cleanup effect in useNavigationBuilder.tsx). (I) First clear occurs in TabNavigator's effect, which successfully clears BaseNavigationContainer's state (sets it to undefined). (II) Second clear comes from StackNavigator unmount. It's useNavigationBuilder cleanup effect also calls NavigationStateContext.setState(undefined). But this time - we meet SceneView as closest NavigationStateContext.Provider. SceneView attempts to merge state change with current navigation state, which is reasonable. But current navigation state should be already undefined... It is, but: ``` [useNavigationBuilder.tsx] const getState = React.useCallback((): State => { const currentState = getCurrentState(); return isStateInitialized(currentState) ? (currentState as State) : (initializedStateRef.current as State); }, [getCurrentState, isStateInitialized]); ``` "undefined" state is treated is non-initialized state, and freshly computed state (initializedStateRef.current) is returned instead. SceneView does merge this old state with `undefined` value, and passes to BaseNavigationContainer. Now we have some legacy global state, despite all navigators being unmounted. After mounting navigators again (3), we can observe old params being restored. These params might come e.g. from old `initialParams` prop (from 1)). Solution: Do not propagate `setState` upwards in `useNavigationBuilder` after state cleanup. This way we'll omit such races.
- Loading branch information