Search code examples
javascriptreact-nativenavigator

React-native: Binding scene to NavigationBar


I'm putting together this Ask/Answer App, and came across this hurdle:

I want to trigger a function in a scene from the navigationBar. Similar to a Login app I have a button in the NavigationBar to Submit an answer:

RightButton(route, navigator, index, navState) {
    if (route.name=='TextInputView'){
     return <TouchableHighlight
             underlayColor="transparent"
             style={{ marginRight:13,  marginTop:0}}
             onPress={() => this.refs.TextInputView.submitSolution()}>
        <Text style={ styles.leftNavButtonText }>Done</Text>
      </TouchableHighlight>
    }

the renderscene(route,navigator) looks like so:

if (route.name == 'TextInputView'){
  return <TextInputView ref="TextInputView" navigator={navigator} {...route.passProps}/>
}

and of course in the "TextInputView" component I have a "submitSolution" function...

problem is if I'm in the scene and press the "Done" - I always get: "undefined is not an object (evaluating '_this2.refs.TextInputView')

as always: thanks for your help


Solution

  • Well, you can do it by keeping everything related to the submission in the scene itself.

    How?

    You do it by injecting a button in the navbar on-demand.

    Here's a solution I created for a project I'm working on: https://rnplay.org/apps/dS31zw

    The idea is to supply the navbar route with an object that has a label and a function (and whatever you like.. e.g. icons.)

    As a bonus, you can inject the title too!

    Note: this trick depends on componentWillMount() being invoked before NavigationBarRouteMapper does it work. If this change in the future, it will definitely break it. But it works flawlessly now!

    'use strict';
    
    import React, {Component} from 'react';
    import ReactNative from 'react-native';
    
    const {
      AppRegistry,
      StyleSheet,
      Text,
      View,
      Navigator,
      Alert,
      TouchableHighlight
    } = ReactNative;
    
    class Home extends Component {
    
      //This trick depends on that componentWillMount fires before the navbar is created
      componentWillMount() {
            this.props.route.navbarTitle = "Home";
    
            this.props.route.rightNavButton = {
                text: "Button",
                onPress: this._doSomething.bind(this)
            };
        }
    
      _doSomething() {
        Alert.alert(
          'Awesome, eh?',
          null,
          [
            {text: 'Indeed'},
          ]
        )
      }
    
      render() {
        return (
          <View style={styles.container}>
                <Text>You are home</Text>
            </View>
        );
      }
    }
    
    class AppContainer extends Component {
    
        renderScene(route, navigator) {
            switch(route.name) {
                case "Home":
          //You must pass route as a prop for this trick to work properly
                return <Home route={route} navigator={navigator} {...route.passProps}  />
                default:
                return (
            <Text route={route}
            style={styles.container}>
                Your route name is probably incorrect {JSON.stringify(route)}
                </Text>
          );
            }
        }
    
      render() {
        return (
          <Navigator
            navigationBar={
              <Navigator.NavigationBar
                style={ styles.navbar }
                routeMapper={ NavigationBarRouteMapper } />
            }
    
            initialRoute={{ name: 'Home' }}
            renderScene={ this.renderScene }
    
          />
        );
      }
    }
    
    //Nothing fancy here, except for checking for injected buttons.
    var NavigationBarRouteMapper = {
      LeftButton(route, navigator, index, navState) {
        if(route.leftNavButton) {
          return (
            <TouchableHighlight
            style={styles.leftNavButton}
            underlayColor="transparent"
            onPress={route.leftNavButton.onPress}>
              <Text style={styles.navbarButtonText}>{route.leftNavButton.text}</Text>
            </TouchableHighlight>
          );
        }
        else if(route.enableBackButton) {
          return (
            <TouchableHighlight
            style={styles.leftNavButton}
            underlayColor="transparent"
            onPress={() => navigator.pop() }>
              <Text style={styles.navbarButtonText}>Back</Text>
            </TouchableHighlight>
          );
        }
      },
      RightButton(route, navigator, index, navState) {
        if(route.rightNavButton) {
          return (
            <TouchableHighlight
            style={styles.rightNavButton}
            underlayColor="transparent"
            onPress={route.rightNavButton.onPress}>
              <Text style={styles.navbarButtonText}>{route.rightNavButton.text}</Text>
            </TouchableHighlight>
          );
        }
      },
      Title(route, navigator, index, navState) {
        return (<Text style={styles.navbarTitle}>{route.navbarTitle || route.name}</Text>);
      }
    };
    
    var styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
        marginTop: 66
      },
      navbar: {
        backgroundColor: '#ffffff',
      },
      navbarTitle: {
        marginVertical: 10,
        fontSize: 17
      },
      leftNavButton: {
        marginVertical: 10,
        paddingLeft: 8,
     },
      rightNavButton: {
        marginVertical: 10,
        paddingRight: 8,
      },
      navbarButtonText: {
        fontSize: 17,
        color: "#007AFF"
      }
    });
    
    AppRegistry.registerComponent('AppContainer', () => AppContainer);