Skip to content

Commit b49e1bd

Browse files
authored
Merge pull request #53466 from software-mansion-labs/fix/use-navigation-reset-on-layout-change
The central page page is not rendered when resized to narrow and then to wide view again
2 parents c1454f7 + de1a3d3 commit b49e1bd

File tree

4 files changed

+128
-4
lines changed

4 files changed

+128
-4
lines changed

src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {ParamListBase} from '@react-navigation/native';
22
import {createNavigatorFactory} from '@react-navigation/native';
3-
import useNavigationResetOnLayoutChange from '@libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange';
3+
import useNavigationResetRootOnLayoutChange from '@libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange';
44
import createPlatformStackNavigatorComponent from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent';
55
import defaultPlatformStackScreenOptions from '@libs/Navigation/PlatformStackNavigation/defaultPlatformStackScreenOptions';
66
import type {PlatformStackNavigationEventMap, PlatformStackNavigationOptions, PlatformStackNavigationState} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -12,7 +12,7 @@ const ResponsiveStackNavigatorComponent = createPlatformStackNavigatorComponent(
1212
createRouter: CustomRouter,
1313
defaultScreenOptions: defaultPlatformStackScreenOptions,
1414
useCustomState: useStateWithSearch,
15-
useCustomEffects: useNavigationResetOnLayoutChange,
15+
useCustomEffects: useNavigationResetRootOnLayoutChange,
1616
ExtraContent: RenderSearchRoute,
1717
});
1818

src/libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1+
import type {ParamListBase} from '@react-navigation/native';
12
import {useEffect} from 'react';
23
import useResponsiveLayout from '@hooks/useResponsiveLayout';
34
import navigationRef from '@libs/Navigation/navigationRef';
5+
import type {CustomEffectsHookProps} from '@libs/Navigation/PlatformStackNavigation/types';
46

5-
function useNavigationResetOnLayoutChange() {
7+
/**
8+
* This hook resets the navigation root state when changing the layout size, resetting the state calls the getRehydredState method in CustomFullScreenRouter.tsx.
9+
* When the screen size is changed, it is necessary to check whether the application displays the content correctly.
10+
* When the app is opened on a small layout and the user resizes it to wide, a second screen has to be present in the navigation state to fill the space.
11+
*/
12+
function useNavigationResetOnLayoutChange({navigation}: CustomEffectsHookProps<ParamListBase>) {
613
const {shouldUseNarrowLayout} = useResponsiveLayout();
714

815
useEffect(() => {
916
if (!navigationRef.isReady()) {
1017
return;
1118
}
12-
navigationRef.resetRoot(navigationRef.getRootState());
19+
navigation.reset(navigation.getState());
20+
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
1321
}, [shouldUseNarrowLayout]);
1422
}
1523

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {useEffect} from 'react';
2+
import useResponsiveLayout from '@hooks/useResponsiveLayout';
3+
import navigationRef from '@libs/Navigation/navigationRef';
4+
5+
/**
6+
* This hook resets the navigation root state when changing the layout size, resetting the state calls the getRehydredState method in CustomRouter.ts.
7+
* When the screen size is changed, it is necessary to check whether the application displays the content correctly.
8+
* When the app is opened on a small layout and the user resizes it to wide, a second screen has to be present in the navigation state to fill the space.
9+
*/
10+
function useNavigationResetRootOnLayoutChange() {
11+
const {shouldUseNarrowLayout} = useResponsiveLayout();
12+
13+
useEffect(() => {
14+
if (!navigationRef.isReady()) {
15+
return;
16+
}
17+
navigationRef.resetRoot(navigationRef.getRootState());
18+
}, [shouldUseNarrowLayout]);
19+
}
20+
21+
export default useNavigationResetRootOnLayoutChange;

tests/ui/ResizeScreenTests.tsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import {NavigationContainer} from '@react-navigation/native';
2+
import {render, renderHook} from '@testing-library/react-native';
3+
import React from 'react';
4+
import useResponsiveLayout from '@hooks/useResponsiveLayout';
5+
import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types';
6+
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
7+
import createResponsiveStackNavigator from '@libs/Navigation/AppNavigator/createResponsiveStackNavigator';
8+
import BottomTabNavigator from '@libs/Navigation/AppNavigator/Navigators/BottomTabNavigator';
9+
import useNavigationResetRootOnLayoutChange from '@libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange';
10+
import navigationRef from '@libs/Navigation/navigationRef';
11+
import type {AuthScreensParamList} from '@libs/Navigation/types';
12+
import ProfilePage from '@pages/settings/Profile/ProfilePage';
13+
import NAVIGATORS from '@src/NAVIGATORS';
14+
import SCREENS from '@src/SCREENS';
15+
16+
const RootStack = createResponsiveStackNavigator<AuthScreensParamList>();
17+
18+
jest.mock('@hooks/useResponsiveLayout', () => jest.fn());
19+
jest.mock('@libs/getIsNarrowLayout', () => jest.fn());
20+
21+
jest.mock('@pages/settings/InitialSettingsPage');
22+
jest.mock('@pages/settings/Profile/ProfilePage');
23+
jest.mock('@libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar');
24+
25+
const DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE: ResponsiveLayoutResult = {
26+
shouldUseNarrowLayout: true,
27+
isSmallScreenWidth: true,
28+
isInNarrowPaneModal: false,
29+
isExtraSmallScreenHeight: false,
30+
isMediumScreenWidth: false,
31+
isLargeScreenWidth: false,
32+
isExtraSmallScreenWidth: false,
33+
isSmallScreen: false,
34+
onboardingIsMediumOrLargerScreenWidth: false,
35+
};
36+
37+
const INITIAL_STATE = {
38+
routes: [
39+
{
40+
name: NAVIGATORS.BOTTOM_TAB_NAVIGATOR,
41+
state: {
42+
index: 1,
43+
routes: [{name: SCREENS.HOME}, {name: SCREENS.SETTINGS.ROOT}],
44+
},
45+
},
46+
],
47+
};
48+
49+
const mockedGetIsNarrowLayout = getIsNarrowLayout as jest.MockedFunction<typeof getIsNarrowLayout>;
50+
const mockedUseResponsiveLayout = useResponsiveLayout as jest.MockedFunction<typeof useResponsiveLayout>;
51+
52+
describe('Resize screen', () => {
53+
it('Should display the settings profile after resizing the screen with the settings page opened to the wide layout', () => {
54+
// Given the initialized navigation on the narrow layout with the settings screen
55+
mockedGetIsNarrowLayout.mockReturnValue(true);
56+
mockedUseResponsiveLayout.mockReturnValue({...DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE, shouldUseNarrowLayout: true});
57+
58+
const {rerender} = renderHook(() => useNavigationResetRootOnLayoutChange());
59+
60+
render(
61+
<NavigationContainer
62+
ref={navigationRef}
63+
initialState={INITIAL_STATE}
64+
>
65+
<RootStack.Navigator>
66+
<RootStack.Screen
67+
name={NAVIGATORS.BOTTOM_TAB_NAVIGATOR}
68+
component={BottomTabNavigator}
69+
/>
70+
71+
<RootStack.Screen
72+
name={SCREENS.SETTINGS.PROFILE.ROOT}
73+
component={ProfilePage}
74+
/>
75+
</RootStack.Navigator>
76+
</NavigationContainer>,
77+
);
78+
79+
const rootStateBeforeResize = navigationRef.current?.getRootState();
80+
81+
expect(rootStateBeforeResize?.routes.at(0)?.name).toBe(NAVIGATORS.BOTTOM_TAB_NAVIGATOR);
82+
expect(rootStateBeforeResize?.routes.at(1)).toBeUndefined();
83+
84+
// When resizing the screen to the wide layout
85+
mockedGetIsNarrowLayout.mockReturnValue(false);
86+
mockedUseResponsiveLayout.mockReturnValue({...DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE, shouldUseNarrowLayout: false});
87+
rerender({});
88+
89+
const rootStateAfterResize = navigationRef.current?.getRootState();
90+
91+
// Then the settings profile page should be displayed on the screen
92+
expect(rootStateAfterResize?.routes.at(0)?.name).toBe(NAVIGATORS.BOTTOM_TAB_NAVIGATOR);
93+
expect(rootStateAfterResize?.routes.at(1)?.name).toBe(SCREENS.SETTINGS.PROFILE.ROOT);
94+
});
95+
});

0 commit comments

Comments
 (0)