navigation: Error when trying to switch components in a scene[Android]

Note: Looks like i seem to have the most issues opened here and i am not terribly keen on that title but this one has me stumped. I re-read the docs and searched the issue list and SO but just couldn’t find a resolution for my latest issue. An Android-only issue again.

I am getting the following (red screen)error when i attempt to switch components in my main entry file:

Trying to remove a view index above child count 0
view tag:33 
detail: View tag: 33
children(0):[],
indicesToRemove(1):[0,],
tagsToDelete(1):[29,]
...
...

My App.js does the initial registering etc and i navigate to my Welcome scene as the default entry for the app. Here i expect to start with some initial background API calls etc while presenting an intermediary Loader component to the user. If all goes well i then present the welcome screen to the user. I may use redux or context but for simplicity i now simulate it via a simple setTimer and in-component state. Everything works fine on IoS(in fact i can even auto redirect/navigate to another scene in IoS) but on Android i get the error i listed above. This error only happens when the timer completes and the switch is attempted i.e I get the loader component displayed as expected for 5 seconds and then the error shows up.

My App.js

import React from 'react';
import {Alert, BackHandler} from 'react-native';
import { StateNavigator } from 'navigation';
import {NavigationHandler} from 'navigation-react';
import {NavigationStack} from 'navigation-react-native';
import {MenuProvider} from 'react-native-popup-menu';

import {Provider} from 'react-redux';
import store from './src/redux/store';

import Menu from './src/scenes/Menu/Menu';
import Welcome from './src/scenes/Welcome/Welcome';

const stateNavigator = new StateNavigator([
  {
    key: 'welcome',
    trackCrumbTrail: true
  }, {
    key: 'menu',
    trackCrumbTrail: true
  }
])
const {welcome, menu} = stateNavigator.states;

welcome.renderScene = () => <Welcome/>;
menu.renderScene = () => <Menu/>;

stateNavigator.navigate('welcome');

class App extends React.Component {
  componentDidMount() {
    BackHandler.addEventListener("hardwareBackPress", this._handleHardwareBackPress);
  }
  componentWillUnmount() {
    BackHandler.removeEventListener("hardwareBackPress", this._handleHardwareBackPress)
  }
  _handleHardwareBackPress = (args) => {
    if (!stateNavigator.canNavigateBack(1)) {
      Alert.alert("Show a prompt with OK/CANCEL")
      return true
    }
  }
  render() {
    return (
      <MenuProvider>
        <Provider store={store}>
          <NavigationHandler stateNavigator={stateNavigator}>
            <NavigationStack/>
          </NavigationHandler>
        </Provider>
      </MenuProvider>
    );
  }
}
export default App;

My Welcome.js

import { NavigationContext } from 'navigation-react';
import React, { Component } from 'react';
import { StyleSheet } from 'react-native';
import AppLoaderCmp from '../../components/AppLoader/AppLoader';
import WelcomeCmp from '../../components/Welcome/Welcome';

class Welcome extends Component {
    state={
        loaded: false
    }
  componentDidMount(){
    setTimeout(()=>this.setState({loaded:true}),5000)
  }

  render() {
      return (
      <NavigationContext.Consumer>
        {({stateNavigator}) => {
            return this.state.loaded
            ?<WelcomeCmp stateNavigator={stateNavigator} />
           :  <AppLoaderCmp stateNavigator={stateNavigator} />
        }
    }
      </NavigationContext.Consumer>
    );
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#E91E63'
  },
});

export default Welcome;

My Welcome component

import React from 'react';
import {Button, SafeAreaView, StyleSheet, Text} from 'react-native';
//import {gotoScreen, routesKeys} from '../../routes';

const WelcomeCmp = (props) => <SafeAreaView style={styles.container}>
    <Text>
        Welcome
    </Text>
    <Button
        title="To Menu Screen"
       // onPress={() => gotoScreen(routesKeys.MENU, stateNavigator)}
       onPress={() => console.log('Pressed the Menu button')}
       />
    <Button
        title="To Admin Screen"
        //onPress={() => gotoScreen(routesKeys.ADMIN, stateNavigator)}
        onPress={() => console.log('Pressed the Admin button')}
        />
    <Button title="Open SideDrawer" 
        //onPress={this._openDrawer}
        onPress={() => console.log('Pressed the SideDrawer button')}

        />
</SafeAreaView>

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#E91E63'
    }
});

export default WelcomeCmp;

My AppLoader component:

import React from 'react';
import {SafeAreaView, StyleSheet, Text} from 'react-native';

const AppLoaderCmp = (props) => <SafeAreaView style={styles.container}>
    <Text>
       App Loader
    </Text>
</SafeAreaView>
const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#E91E63'
    }
});

export default AppLoaderCmp;

I am using the latest version Navigation React Native since i did an unlink/remove and re-added the libraries:

...
    "navigation": "^5.2.0",
    "navigation-react": "^4.1.1",
    "navigation-react-native": "^5.4.0",
...

I initially had a slightly more ‘twisted’ setup. I only did a renderScene() for my entry scene, and wanted to dynamically render the scene on demand where required. It does work though i am not sure if it is optimal. Ive reverted back to a simple example to check if the issue can be reproduced. It can. I also tried via checks in componentDidUpdate but get the same error. All my attempts of course work perfectly fine in IoS.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 15 (15 by maintainers)

Most upvoted comments

I definitely will. Currently i am re-architecting my (rather verbose/code-heavy)app to use React Hooks - first attempt at it - and Typescript(first official attempt). This is after attempting to change the structure to re-ducks, an enhanced duck pattern. Using hooks may sort of nullify the need for adhering to a strict duck pattern, and i am still figuring out an optimal approach.

There are a few expected and unexpected stumbling blocks and am currently prioritising fixing these issues. Including the current urgent need to to integrate Crashlytics and/or Flipper since the app crashes inexplicably at odd times and so far haven’t found the reason why. My current guess is that its inefficient use of one or more useEffect hooks. And the all important reason why auth requests to Firebase isn’t just completing as it did just a few days ago.

Will send PRs/ideas for consideration as soon as the big issues are done with. As of now i suspect it may be Android-only but with some more thought, maybe cross-platform.

[Incidentally, the inspiration to move to hooks and typescript came courtesy reading the Navigation Router source and examples]

You must move the MenuProvider inside your scene. Its name is confusing. It isn’t like a ReduxProvider because it renders UI elements.

const Welcome = props => (<View style={{flex:1}}> 
<MenuProvider
  <Menu
    renderer={renderers.SlideInMenu}
    onSelect={value => alert(`Selected number: ${value}`)}>
    <MenuTrigger text='Select option'/>
    <MenuOptions>
      <MenuOption value={1} text='One'/>
      <MenuOption value={2}>
        <Text style={{ color: 'red'}}> 
          Two
        </Text>
      </MenuOption>
      <MenuOption value={3} disabled={true} text='Three'/>
    </MenuOptions>
  </Menu> 
</MenuProvider
</View>);

It is caused by the Navigation router. The Navigation router doesn’t like it when you change the first child of a Scene. I won’t reopen this though because I think it’s fair to assume the first child won’t change