Michael Hoefler
Michael Hoefler

Reputation: 93

How to use react navigation outside of a screen

I would like to make my app move to the next page when a code is entered correctly, but I have been having much trouble doing so. I am working in a file names AccessForm.js , which is not a screen but is a component that is included in the access code screen. I tried using this.props.navigation.navigate('CreateAccountScreen');, but ran into the error "Undefined is not an object (evaluating 'this.props.navigation'). With some trial and error, I found out that I can only use react-navigation inside of an actual screen for some weird reason. After this, I made an attempt to use this.state and this.setState({})to keep track of a screen variable, and sync it to the actual access code screen, so i could use navigation. Unfortunately, this.setState also throws a "Undefined is not an object" error. I have pasted an abbreviated version of my code below. What would be the best way to achieve this navigating outside of a screen file issue?

App.js ---->

import { createStackNavigator, createAppContainer } from 'react-navigation';

import AccessScreen from './src/screens/AccessScreen';
import CreateAccountScreen from './src/screens/CreateAccountScreen';

const RootStack = createStackNavigator ({
    EnterAccessCode : {
        screen: AccessScreen
    },
    CreateAccount : {
        screen: CreateAccountScreen
    }
},
{
  headerMode: 'none'
});

const App = createAppContainer(RootStack);

export default App;

AccessForm.js ---->

import React from 'react';
import { StyleSheet, Text, View, TextInput, AlertIOS } from 'react-native';


var firebase = require("firebase");

if (!firebase.apps.length) { // Don't open more than one firebase session
  firebase.initializeApp({ // Initialize firebase connection
    apiKey: "key",
    authDomain: "domain",
    databaseURL: "url",
    storageBucket: "storage_bucket",
  });
}

this.codesRef = firebase.database().ref('codes'); // A reference to the codes section in the db

// this.state = {
//   screen: 0
// };

export default class LoginForm extends React.Component {
  constructor(props) {
    super(props);
    //this.checkCode = this.checkCode.bind(this); // throws error
  }
  render() {
    return (
      <View style={styles.container} >
        <TextInput
            style={styles.input}
            placeholder='Access Code'
            returnKeyType='go'
            onSubmitEditing={(text) => checkCode(text.nativeEvent.text)} // Checks the code entered
            autoCapitalize='none'
            autoCorrect={false}
        />
      </View>
    );
  }
}


function checkCode(text) {
  var code = text; // Set entered code to the var "code"
  var identifier = ""; // Used to store unique code object identifier
  codesRef.once('value', function(db_snapshot) {
    let codeIsFound = false
    db_snapshot.forEach(function(code_snapshot) { // Cycle through available codes in db
      if (code == code_snapshot.val().value) { // Compare code to db code
        codeIsFound = true;
        identifier = code_snapshot.key; // Code object ID
      }
    })
    if (codeIsFound) {
      deleteCode(identifier); // Delete the code if used, maybe do this after account is created?
      this.props.navigation.navigate('CreateAccountScreen');
      //this.setState({screen: 1}); // this throws error
      // MOVE TO NEXT SCREEN
      //this.props.navigation.navigate('AccountCreateScreen'); // throws error
    } else { // wrong code
      // note to self : add error message based on state var
      AlertIOS.alert("We're Sorry...", "The code you entered was not found in the database! Please contact Mr. Gibson for further assistance.");
    }
  });
}

function deleteCode(id) { // delete a code from unique ID
  firebase.database().ref('codes/' + id).remove();
}

// stylesheet is below

Login.js ---->

import React from 'react';
import { StyleSheet, Text, View, Image, TextInput, KeyboardAvoidingView, Platform } from 'react-native';
import AccessForm from './AccessForm';

export default class App extends React.Component {
  render() {
    return (
        <View>
            <View style={styles.logoContainer}>
                <Image 
                    source={require('../images/mhs.jpg')}
                    style={styles.logo}
                />
                <Text style={styles.app_title}>MHS-Protect</Text>
                <Text>An app to keep MHS safe and in-touch.</Text>
            </View>
            <KeyboardAvoidingView style={styles.container} behavior='padding'>
              <View style ={styles.formContainer}>
                  <AccessForm/>
              </View>
            </KeyboardAvoidingView>
        </View>
    );
  }
}

//styles below

Upvotes: 4

Views: 4941

Answers (3)

Md. Robi Ullah
Md. Robi Ullah

Reputation: 2172

Copy and paste(Reference) from: https://github.com/react-navigation/react-navigation/issues/1439#issuecomment-303661539

It works from me.

you can pass your top-level navigator ref to a service, and dispatch actions from that service.

// App.js

import NavigatorService from './services/navigator';

const Navigator = StackNavigator({ /* ... */ })

class App extends Component {
  // ...

  render(): {
    return (
      <Navigator
        ref={navigatorRef => {
          NavigatorService.setContainer(navigatorRef);
        }}
      />
    );
  }
}
// services/navigator.js
// @flow

import { NavigationActions } from 'react-navigation';
import type { NavigationParams, NavigationRoute } from 'react-navigation';

let _container; // eslint-disable-line

function setContainer(container: Object) {
  _container = container;
}

function reset(routeName: string, params?: NavigationParams) {
  _container.dispatch(
    NavigationActions.reset({
      index: 0,
      actions: [
        NavigationActions.navigate({
          type: 'Navigation/NAVIGATE',
          routeName,
          params,
        }),
      ],
    }),
  );
}

function navigate(routeName: string, params?: NavigationParams) {
  _container.dispatch(
    NavigationActions.navigate({
      type: 'Navigation/NAVIGATE',
      routeName,
      params,
    }),
  );
}

function navigateDeep(actions: { routeName: string, params?: NavigationParams }[]) {
  _container.dispatch(
    actions.reduceRight(
      (prevAction, action): any =>
        NavigationActions.navigate({
          type: 'Navigation/NAVIGATE',
          routeName: action.routeName,
          params: action.params,
          action: prevAction,
        }),
      undefined,
    ),
  );
}

function getCurrentRoute(): NavigationRoute | null {
  if (!_container || !_container.state.nav) {
    return null;
  }

  return _container.state.nav.routes[_container.state.nav.index] || null;
}

export default {
  setContainer,
  navigateDeep,
  navigate,
  reset,
  getCurrentRoute,
};

and then you can use Navigator service everywhere.

Like:

import NavigatorService from './services/navigator';

NavigatorService.navigate('Home');

Upvotes: 0

Ziyo
Ziyo

Reputation: 604

import React from 'react';
import { StyleSheet, Text, View, TextInput, AlertIOS } from 'react-native';

var firebase = require('firebase');

if (!firebase.apps.length) {
  // Don't open more than one firebase session
  firebase.initializeApp({
    // Initialize firebase connection
    apiKey: 'key',
    authDomain: 'domain',
    databaseURL: 'url',
    storageBucket: 'storage_bucket',
  });
}

export default class LoginForm extends React.Component {
  constructor(props) {
    super(props);
    this.codesRef = firebase.database().ref('codes'); // A reference to the codes section in the db
  }

  checkCode = text => {
    var code = text; // Set entered code to the var "code"
    var identifier = ''; // Used to store unique code object identifier
    this.codesRef.once('value', function(db_snapshot) {
      let codeIsFound = false;
      db_snapshot.forEach(function(code_snapshot) {
        // Cycle through available codes in db
        if (code == code_snapshot.val().value) {
          // Compare code to db code
          codeIsFound = true;
          identifier = code_snapshot.key; // Code object ID
        }
      });
      if (codeIsFound) {
        this.deleteCode(identifier); // Delete the code if used, maybe do this after account is created?
        this.props.navigation.navigate('CreateAccount');
      } else {
        // wrong code
        // note to self : add error message based on state var
        AlertIOS.alert(
          "We're Sorry...",
          'The code you entered was not found in the database! Please contact Mr. Gibson for further assistance.'
        );
      }
    });
  };

  deleteCode = id => {
    firebase
      .database()
      .ref('codes/' + id)
      .remove();
  };

  render() {
    return (
      <View style={styles.container}>
        <TextInput
          style={styles.input}
          placeholder="Access Code"
          returnKeyType="go"
          onSubmitEditing={text => this.checkCode(text.nativeEvent.text)} // Checks the code entered
          autoCapitalize="none"
          autoCorrect={false}
        />
      </View>
    );
  }
}

Upvotes: 2

Huy Ngo
Huy Ngo

Reputation: 382

You should have navigation object in your props. By default, react navigation will pass navigation to all screens but other components. To do this, you have two options:
1. Pass navigation props from your screen to every child components (not recommended).
2. Use withNavigation as mention in document here https://reactnavigation.org/docs/en/connecting-navigation-prop.html

import React from 'react';
import { Button } from 'react-native';
import { withNavigation } from 'react-navigation';

class MyBackButton extends React.Component {
  render() {
    return <Button title="Back" onPress={() => { this.props.navigation.goBack() }} />;
  }
}

// withNavigation returns a component that wraps MyBackButton and passes in the
// navigation prop
export default withNavigation(MyBackButton);

Edit: The checkCode method does not belong to your LoginForm. You need to:
1. Make it part of LoginForm .
2. Remember to use bind or arrow function definition. Otherwise, your this inside function is not defined.

import { withNavigation } from 'react-navigation';
class LoginForm extends React.Component {
    checkCode = (text) => {
        ....
    };
}
export default withNavigation(LoginForm);

You can read more about bind or arrow method here https://medium.com/shoutem/react-to-bind-or-not-to-bind-7bf58327e22a

Upvotes: 1

Related Questions