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
Avoid running side-effects in didMoveToWin after invalidation (#700)
## Description
This PR resolves an issue on iOS where we'd run a side-effect code in didMoveToWindow after the view has been invalidated (that is removed from the react-native tree). As a result we'd end up getting the following crash:
```
UIViewControllerHierarchyInconsistency child view controller:<UINavigationController: 0xREDACTED should have parent view controller:<RNSScreen: 0xREDACTED but requested parent is:<UINavigationController: 0xREDACTED>
```
The problem turned out to be related to the fact we've been attempting to reattach view to a different view controller than the one it's been previously attached to. However, the code that attaches it should not be run in the first place as the view has already been removed. After investigating it turned out that layout animations sometimes cause didMoveToWindow run despite the fact the view has been removed from the hierarchy. In this PR we add an extra check to avoid running side-effects from that callback when the view is invalidated.
## Changes
We change didMoveToWindow logic in both RNSScreenStack and RNSScreenContainer such that we only run side-effect when the view has not been invalidated. To check that we add a marker _invalidated that is set in invalidate callback.
## Test code and steps to reproduce
The below app can be used to reproduce the crash. Before this change the app would crash after pressing the button "move" twice. After this change the app no longer crashes.
```js
import React, { Component, useState } from 'react';
import {
StyleSheet,
Button,
View,
Text,
LayoutAnimation,
} from 'react-native';
import {
Screen,
ScreenStack,
ScreenStackHeaderConfig,
ScreenContainer,
enableScreens,
} from 'react-native-screens';
enableScreens(true);
function Step({ move, title, color }) {
return (
<View style={[styles.view, { backgroundColor: color }]}>
<Button title="Move" onPress={move} />
</View>
);
}
function App() {
const [state, setState] = useState(0);
if (state === 2) return <Step move={() => setState(0)} color="green" />;
return (
<ScreenStack style={styles.container} key={state < 2 ? 'one' : 'two'}>
<Screen stackPresentation="containedModal" style={styles.container}>
<ScreenStack style={styles.container}>
<Screen style={styles.container} stackPresentation="containedModal">
<ScreenStack style={styles.container}>
<Screen style={styles.container}>
<ScreenStackHeaderConfig title={'One'} />
<Step move={() => setState(1)} color="pink" />
</Screen>
</ScreenStack>
</Screen>
</ScreenStack>
</Screen>
{state === 1 && (
<Screen
style={styles.container}
stackPresentation="containedModal"
stackAnimation="fade">
<ScreenStack style={styles.container}>
<Screen style={styles.container}>
<Step
move={() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
setState(2);
}}
color="blue"
/>
</Screen>
</ScreenStack>
</Screen>
)}
</ScreenStack>
);
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
},
view: {
paddingTop: 100,
...StyleSheet.absoluteFillObject,
},
});
export default App;
```
0 commit comments