Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(iOS): Fix conditional rendering on Fabric #1978

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions FabricTestExample/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import Test1726 from './src/Test1726';
import Test1802 from './src/Test1802';
import Test1844 from './src/Test1844';
import Test1864 from './src/Test1864';
import Test1978 from './src/Test1978';

enableFreeze(true);

Expand Down
90 changes: 90 additions & 0 deletions FabricTestExample/src/Test1978.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { NavigationContainer } from '@react-navigation/native';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import React, { useState } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';

type StackParamList = {
Home: undefined;
Home1: undefined;
Home2: undefined;
Home3: undefined;
};

interface MainScreenProps {
navigation: NativeStackNavigationProp<StackParamList>;
}

const Home = ({ navigation }: MainScreenProps) => (
<View style={styles.view}>
<Text onPress={() => navigation.navigate('Home1')}>
This is the initial View
</Text>
</View>
);

const Home1 = () => (
<View style={styles.view}>
<Text>This is View 1</Text>
</View>
);

const Home2 = ({ navigation }: MainScreenProps) => (
<View style={styles.view}>
<Text onPress={() => navigation.navigate('Home3')}>This is View 2</Text>
</View>
);

const Home3 = () => (
<View style={styles.view}>
<Text>This is View 3</Text>
</View>
);

const Stack = createNativeStackNavigator();

const Test1978 = () => {
const [hasChangedState, setHasChangedState] = useState(true);

return (
<NavigationContainer>
<Stack.Navigator>
{hasChangedState ? (
<>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Home1" component={Home1} />
</>
) : (
<>
<Stack.Screen name="Home2" component={Home2} />
<Stack.Screen name="Home3" component={Home3} />
</>
)}
</Stack.Navigator>
<TouchableOpacity
style={styles.button}
onPress={() => setHasChangedState(old => !old)}>
<Text>Change state</Text>
</TouchableOpacity>
</NavigationContainer>
);
};

const styles = StyleSheet.create({
button: {
justifyContent: 'center',
alignItems: 'center',
height: 100,
},
view: {
alignItems: 'center',
backgroundColor: '#b7c4bb',
flex: 1,
justifyContent: 'center',
padding: 12,
},
});

export default Test1978;
1 change: 1 addition & 0 deletions TestsExample/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import Test1791 from './src/Test1791';
import Test1802 from './src/Test1802';
import Test1844 from './src/Test1844';
import Test1864 from './src/Test1864';
import Test1978 from './src/Test1978';

enableFreeze(true);

Expand Down
90 changes: 90 additions & 0 deletions TestsExample/src/Test1978.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { NavigationContainer } from '@react-navigation/native';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import React, { useState } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';

type StackParamList = {
Home: undefined;
Home1: undefined;
Home2: undefined;
Home3: undefined;
};

interface MainScreenProps {
navigation: NativeStackNavigationProp<StackParamList>;
}

const Home = ({ navigation }: MainScreenProps) => (
<View style={styles.view}>
<Text onPress={() => navigation.navigate('Home1')}>
This is the initial View
</Text>
</View>
);

const Home1 = () => (
<View style={styles.view}>
<Text>This is View 1</Text>
</View>
);

const Home2 = ({ navigation }: MainScreenProps) => (
<View style={styles.view}>
<Text onPress={() => navigation.navigate('Home3')}>This is View 2</Text>
</View>
);

const Home3 = () => (
<View style={styles.view}>
<Text>This is View 3</Text>
</View>
);

const Stack = createNativeStackNavigator();

const Test1978 = () => {
const [hasChangedState, setHasChangedState] = useState(true);

return (
<NavigationContainer>
<Stack.Navigator>
{hasChangedState ? (
<>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Home1" component={Home1} />
</>
) : (
<>
<Stack.Screen name="Home2" component={Home2} />
<Stack.Screen name="Home3" component={Home3} />
</>
)}
</Stack.Navigator>
<TouchableOpacity
style={styles.button}
onPress={() => setHasChangedState(old => !old)}>
<Text>Change state</Text>
</TouchableOpacity>
</NavigationContainer>
);
};

const styles = StyleSheet.create({
button: {
justifyContent: 'center',
alignItems: 'center',
height: 100,
},
view: {
alignItems: 'center',
backgroundColor: '#b7c4bb',
flex: 1,
justifyContent: 'center',
padding: 12,
},
});

export default Test1978;
24 changes: 19 additions & 5 deletions ios/RNSScreenStack.mm
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ @implementation RNSScreenStackView {
BOOL _hasLayout;
__weak RNSScreenStackManager *_manager;
BOOL _updateScheduled;
BOOL _isViewRecycled;
#ifdef RCT_NEW_ARCH_ENABLED
UIView *_snapshot;
#endif
Expand Down Expand Up @@ -454,8 +455,8 @@ - (void)setModalViewControllers:(NSArray<UIViewController *> *)controllers

- (void)setPushViewControllers:(NSArray<UIViewController *> *)controllers
{
// when there is no change we return immediately
if ([_controller.viewControllers isEqualToArray:controllers]) {
// when there is no change and the view of the controller is not recycled we return immediately
if ([_controller.viewControllers isEqualToArray:controllers] && !_isViewRecycled) {
return;
}

Expand All @@ -467,7 +468,7 @@ - (void)setPushViewControllers:(NSArray<UIViewController *> *)controllers
// when transition is ongoing, any updates made to the controller will not be reflected until the
// transition is complete. In particular, when we push/pop view controllers we expect viewControllers
// property to be updated immediately. Based on that property we then calculate future updates.
// When the transition is ongoing the property won't be updated immediatly. We therefore avoid
// When the transition is ongoing the property won't be updated immediately. We therefore avoid
// making any updated when transition is ongoing and schedule updates for when the transition
// is complete.
if (_controller.transitionCoordinator != nil) {
Expand Down Expand Up @@ -978,10 +979,23 @@ - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childCompone
@(index),
@([childComponentView.superview tag]));

[_reactSubviews insertObject:(RNSScreenView *)childComponentView atIndex:index];
((RNSScreenView *)childComponentView).reactSuperview = self;
RNSScreenView *screenChildComponent = (RNSScreenView *)childComponentView;
[_reactSubviews insertObject:screenChildComponent atIndex:index];

// Since the view may be recycled we don't want to remount snapshot to the hierarchy.
// Thus, we want to reset view to initial view and force `setPushViewControllers` to be called.
if ([NSStringFromClass([screenChildComponent.controller.view class]) isEqualToString:@"_UIReplicantView"]) {
[screenChildComponent.controller resetViewToScreen];
_isViewRecycled = YES;
}

screenChildComponent.reactSuperview = self;

dispatch_async(dispatch_get_main_queue(), ^{
[self maybeAddToParentAndUpdateContainer];
// We don't need to indicate if the view has been recycled, since we're after the mounting state of the
// recycled view.
self->_isViewRecycled = NO;
});
}

Expand Down