Skip to content

Commit bf3d945

Browse files
feat: disable freeze for first render (#1220)
React-navigation's `useIsFocused` works based on changes between renders. Freezing stops all renders on non-visible screens thus breaking the `useIsFocused` hook. This issue can be mitigated by allowing one more render before freezing the screen / disabling freeze for the first render. This also fixes unnecessary pop animation on stack reset.
1 parent 630ae10 commit bf3d945

File tree

9 files changed

+346
-25
lines changed

9 files changed

+346
-25
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { device, expect, element, by } from 'detox';
2+
3+
describe('Bottom tabs and native stack', () => {
4+
beforeEach(async () => {
5+
await device.reloadReactNative();
6+
});
7+
8+
it('should go to main screen and back', async () => {
9+
await expect(
10+
element(by.id('root-screen-example-BottomTabsAndStack'))
11+
).toBeVisible();
12+
element(by.id('root-screen-example-BottomTabsAndStack')).tap();
13+
14+
await expect(
15+
element(by.id('bottom-tabs-more-details-button'))
16+
).toBeVisible();
17+
});
18+
19+
it('should go to details screen', async () => {
20+
await element(by.id('root-screen-example-BottomTabsAndStack')).tap();
21+
await element(by.id('bottom-tabs-more-details-button')).tap();
22+
await expect(element(by.id('bottom-tabs-more-details-button'))).toHaveLabel(
23+
'More details 1'
24+
);
25+
});
26+
27+
it('should go to details screen and back', async () => {
28+
await element(by.id('root-screen-example-BottomTabsAndStack')).tap();
29+
await element(by.id('bottom-tabs-more-details-button')).tap();
30+
await expect(element(by.id('bottom-tabs-more-details-button'))).toHaveLabel(
31+
'More details 1'
32+
);
33+
if (device.getPlatform() === 'ios') {
34+
await element(by.type('_UIButtonBarButton')).tap();
35+
} else {
36+
await device.pressBack();
37+
}
38+
await expect(element(by.id('bottom-tabs-more-details-button'))).toHaveLabel(
39+
'More details 0'
40+
);
41+
});
42+
43+
it('should go between tabs', async () => {
44+
await element(by.id('root-screen-example-BottomTabsAndStack')).tap();
45+
46+
await element(by.id('bottom-tabs-B-tab')).tap();
47+
await expect(element(by.id('bottom-tabs-header-right-id'))).toHaveText('B');
48+
49+
await element(by.id('bottom-tabs-A-tab')).tap();
50+
await expect(element(by.id('bottom-tabs-header-right-id'))).toHaveText('A');
51+
});
52+
53+
it('should go to first screen on double tap on a tab', async () => {
54+
await element(by.id('root-screen-example-BottomTabsAndStack')).tap();
55+
56+
await element(by.id('bottom-tabs-more-details-button')).tap();
57+
await expect(element(by.id('bottom-tabs-more-details-button'))).toHaveLabel(
58+
'More details 1'
59+
);
60+
61+
await element(by.id('bottom-tabs-A-tab')).multiTap(2);
62+
await expect(element(by.id('bottom-tabs-more-details-button'))).toHaveLabel(
63+
'More details 0'
64+
);
65+
});
66+
67+
it('should keep stack state on tab change', async () => {
68+
await element(by.id('root-screen-example-BottomTabsAndStack')).tap();
69+
70+
await element(by.id('bottom-tabs-more-details-button')).tap();
71+
await element(by.id('bottom-tabs-more-details-button')).tap();
72+
await expect(element(by.id('bottom-tabs-more-details-button'))).toHaveLabel(
73+
'More details 2'
74+
);
75+
76+
await element(by.id('bottom-tabs-B-tab')).tap();
77+
await expect(element(by.id('bottom-tabs-more-details-button'))).toHaveLabel(
78+
'More details 0'
79+
);
80+
await element(by.id('bottom-tabs-A-tab')).tap();
81+
82+
await expect(element(by.id('bottom-tabs-more-details-button'))).toHaveLabel(
83+
'More details 2'
84+
);
85+
});
86+
});

Example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ PODS:
351351
- React-Core
352352
- RNReanimated (1.13.2):
353353
- React-Core
354-
- RNScreens (3.8.0):
354+
- RNScreens (3.9.0):
355355
- React-Core
356356
- React-RCTImage
357357
- RNVectorIcons (8.0.0):
@@ -571,7 +571,7 @@ SPEC CHECKSUMS:
571571
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
572572
RNGestureHandler: 5e58135436aacc1c5d29b75547d3d2b9430d052c
573573
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
574-
RNScreens: 6e1ea5787989f92b0671049b808aef64fa1ef98c
574+
RNScreens: 4d79118be80f79fa1f4aa131909a1d6e86280af3
575575
RNVectorIcons: f67a1abce2ec73e62fe4606e8110e95a832bc859
576576
Yoga: c11abbf5809216c91fcd62f5571078b83d9b6720
577577
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a

Example/src/screens/BottomTabsAndStack.tsx

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useLayoutEffect } from 'react';
2-
import { I18nManager, SafeAreaView, StyleSheet } from 'react-native';
2+
import { I18nManager, StyleSheet, Text, View } from 'react-native';
33
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
44
import {
55
createNativeStackNavigator,
@@ -45,26 +45,34 @@ const DetailsScreen = ({
4545
index < colors.length ? colors[index] : colors[colors.length - 1];
4646

4747
return (
48-
<SafeAreaView
49-
style={{ ...styles.container, backgroundColor: currentColor }}>
48+
<View style={{ ...styles.container, backgroundColor: currentColor }}>
5049
<Button
5150
title={`More details ${index}`}
51+
accessibilityLabel={`More details ${index}`}
52+
testID="bottom-tabs-more-details-button"
5253
onPress={() => navigation.push('Details', { index: index + 1 })}
5354
/>
5455
{index === 0 ? (
55-
<Button onPress={() => navigation.pop()} title="🔙 Back to Examples" />
56+
<Button
57+
onPress={() => navigation.pop()}
58+
title="🔙 Back to Examples"
59+
testID="bottom-tabs-go-back-button"
60+
/>
5661
) : null}
57-
</SafeAreaView>
62+
</View>
5863
);
5964
};
6065

61-
const createStack = () => {
66+
const createStack = (letter: string) => {
6267
const Stack = createNativeStackNavigator();
6368

6469
const makeStack = () => (
6570
<Stack.Navigator
6671
screenOptions={{
6772
direction: I18nManager.isRTL ? 'rtl' : 'ltr',
73+
headerRight: () => (
74+
<Text testID="bottom-tabs-header-right-id">{letter}</Text>
75+
),
6876
}}>
6977
<Stack.Screen name="Details" component={DetailsScreen} />
7078
</Stack.Navigator>
@@ -73,17 +81,25 @@ const createStack = () => {
7381
return makeStack;
7482
};
7583

76-
const AStack = createStack();
77-
const BStack = createStack();
78-
const CStack = createStack();
79-
const DStack = createStack();
84+
const AStack = createStack('A');
85+
const BStack = createStack('B');
86+
const CStack = createStack('C');
87+
const DStack = createStack('D');
8088

8189
const Tab = createBottomTabNavigator();
8290

8391
const NavigationTabsAndStack = (): JSX.Element => (
8492
<Tab.Navigator>
85-
<Tab.Screen name="A" component={AStack} />
86-
<Tab.Screen name="B" component={BStack} />
93+
<Tab.Screen
94+
name="A"
95+
component={AStack}
96+
options={{ tabBarTestID: 'bottom-tabs-A-tab' }}
97+
/>
98+
<Tab.Screen
99+
name="B"
100+
component={BStack}
101+
options={{ tabBarTestID: 'bottom-tabs-B-tab' }}
102+
/>
87103
<Tab.Screen name="C" component={CStack} />
88104
<Tab.Screen name="D" component={DStack} />
89105
</Tab.Navigator>

Example/src/shared/Button.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,23 @@ import { Spacer } from './Spacer';
44

55
interface Props {
66
title: string;
7+
accessibilityLabel?: string;
78
onPress: () => void;
89
testID?: string;
910
}
1011

11-
export const Button = ({ title, onPress, testID }: Props): JSX.Element => (
12+
export const Button = ({
13+
title,
14+
accessibilityLabel,
15+
onPress,
16+
testID,
17+
}: Props): JSX.Element => (
1218
<Spacer>
13-
<RNButton title={title} onPress={onPress} testID={testID} />
19+
<RNButton
20+
accessibilityLabel={accessibilityLabel}
21+
title={title}
22+
onPress={onPress}
23+
testID={testID}
24+
/>
1425
</Spacer>
1526
);

TestsExample/App.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ import Test1166 from './src/Test1166';
6767
import Test1188 from './src/Test1188';
6868
import Test1190 from './src/Test1190';
6969
import TestFreeze from './src/TestFreeze';
70+
import Test1198 from './src/Test1198';
7071
import Test1204 from './src/Test1204';
72+
import Test1209 from './src/Test1209';
7173
import Test1214 from './src/Test1214';
7274

7375
enableFreeze(true);

TestsExample/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ PODS:
376376
- React-RCTVibration
377377
- ReactCommon/turbomodule/core
378378
- Yoga
379-
- RNScreens (3.7.0):
379+
- RNScreens (3.9.0):
380380
- React-Core
381381
- React-RCTImage
382382
- RNVectorIcons (7.1.0):
@@ -592,7 +592,7 @@ SPEC CHECKSUMS:
592592
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
593593
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
594594
RNReanimated: b04ef2a4f0cb61b062bbcf033f84a9e470f4f60b
595-
RNScreens: 2a71d74b4e530f051f5684c8111d7591176722cf
595+
RNScreens: 4d79118be80f79fa1f4aa131909a1d6e86280af3
596596
RNVectorIcons: bc69e6a278b14842063605de32bec61f0b251a59
597597
Yoga: c11abbf5809216c91fcd62f5571078b83d9b6720
598598
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a

TestsExample/src/Test1198.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React, {useEffect} from 'react';
2+
import {NavigationContainer, useIsFocused} from '@react-navigation/native';
3+
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
4+
import {Text, View} from 'react-native';
5+
6+
const BottomTabs = createBottomTabNavigator();
7+
8+
function Home() {
9+
return (
10+
<View
11+
style={{
12+
backgroundColor: 'lightgrey',
13+
flex: 1,
14+
alignItems: 'center',
15+
justifyContent: 'center',
16+
}}>
17+
<Text>Home</Text>
18+
</View>
19+
);
20+
}
21+
22+
function Settings() {
23+
const isFocused = useIsFocused();
24+
const fetch = () => console.log('refetching data!');
25+
26+
useEffect(() => {
27+
if (isFocused) {
28+
fetch();
29+
}
30+
}, [isFocused]);
31+
32+
console.log({isFocused});
33+
34+
return (
35+
<View
36+
style={{
37+
backgroundColor: 'wheat',
38+
flex: 1,
39+
alignItems: 'center',
40+
justifyContent: 'center',
41+
}}>
42+
<Text>Settings</Text>
43+
</View>
44+
);
45+
}
46+
47+
const App = () => (
48+
<NavigationContainer>
49+
<BottomTabs.Navigator>
50+
<BottomTabs.Screen name="Home" component={Home} />
51+
<BottomTabs.Screen name="Settings" component={Settings} />
52+
</BottomTabs.Navigator>
53+
</NavigationContainer>
54+
);
55+
56+
export default App;

0 commit comments

Comments
 (0)