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
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>
);
}
}
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
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');
Related
I am working on a simple React Native app to read and write data from a Firebase database. My read and write permissions in Firebase have been set to true:
{
"rules": {
".read": true,
".write": true,
}
}
Here are my relevant files:
App.js
import React from 'react'
import {View, StyleSheet, Button} from 'react-native'
import * as firebase from 'firebase'
import RootStackNavigator from './navigation/RootNavigation'
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoadingComplete: false
}
var firebaseConfig = {
apiKey: "xxxxxxx",
authDomain: "testproject-9d0bc.firebaseapp.com",
databaseURL: "https://testproject-9d0bc-default-rtdb.firebaseio.com",
projectId: "testproject-9d0bc",
storageBucket: "testproject-9d0bc.appspot.com",
messagingSenderId: "1003049293166",
appId: "1:1003049293166:web:1df37fd6d181cf895cdd7f"
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig)
} else {
firebase.app()
}
}
render() {
return (
<View style={styles.container}>
<RootStackNavigator/>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
RootStackNavigation.js
import React from 'react';
import { createAppContainer, StackNavigator } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import HomeScreen from '../src/screens/HomeScreen';
const RootStackNavigator = createStackNavigator(
{
screen: HomeScreen
},
{
initialRouteName:"screen", // first component that should be displayed
defaultNavigationOptions: {
title: "App"
}
}
);
export default createAppContainer(RootStackNavigator)
HomeScreen.js
import React from "react";
import { Text, StyleSheet, View, Button, TouchableOpacity } from "react-native";
import * as firebase from "firebase";
const HomeScreen = (
props
) => {
function getData() {
firebase
.database()
.ref("people/")
.on("value", (snapshot) => {
const age = snapshot.val().age;
console.log("Age: " + age);
});
}
return (
<View>
<Text>Hello this is the home screen</Text>
<Button title="Get Data" onPress={getData} />
</View>
);
};
const styles = StyleSheet.create({
text: {
fontSize: 30,
},
});
export default HomeScreen;
My firebase database looks like this (all data was manually added through the firebase website)and I want to be able to print out these items after pressing the "Get Data" button to the console as shown in my getData function in HomeScreen.js
However, my code in getData does not work and nothing prints out to my console. What am I missing here?
Kindly modify this in your code
firebase.database.ref("people").on("value", snapshots => {
let peoples = [];
snapshots.forEach((snapshot) => {
peoples.push(snapshot.val().age);
console.log(snapshot.val().age);
});
// here you can set this Array
// setPeople(peoples)
Figured out my issue. As you can see in my firebase database, the way I was accessing my data was wrong. If I wanted to get the age of "dad", I should have called snapshot.val().dad.
I am new to using redux for React Native and am testing it with a simple case. I have been able to successfully connect to the store, and I can see the action is dispatched properly using the redux debugger, however, the store is not updating in the debugger. I've tried several different implementations, but nothing is working. Any help would be appreciated!
Component:
import React, { PureComponent } from 'react'
import { Text, TouchableOpacity, SafeAreaView, Alert, Button } from 'react-native'
import { Navigation } from 'react-native-navigation';
import { connect } from 'react-redux'
import simpleAction from '../store/actions/simpleAction'
class App2 extends PureComponent {
constructor(props){
super(props);
}
pressRedux = () => {
const data = 'hello'
this.props.simpleAction(data)
}
render() {
return (
<SafeAreaView>
<Text>
{this.props.state.simpleReducer.text}
</Text>
<Button onPress = {this.pressRedux} title = 'Redux' />
</SafeAreaView>
)
}
}
function mapStateToProps(state) {
return {
state: state
};
}
const mapDispatchToProps = {
simpleAction
}
export default connect(mapStateToProps, mapDispatchToProps)(App2);
Action:
import {SET_TEXT} from '../types/types'
export default function simpleAction(data) {
return({
type: SET_TEXT,
payload: data
})
}
reducer:
import SET_TEXT from '../types/types'
const INITIAL_STATE = {
text: 'Hi'
}
const simpleReducer = (state = INITIAL_STATE, action ) => {
switch(action.type){
case SET_TEXT:
return { ...state, text: action.payload};
default:
return state;
}
}
export default simpleReducer;
The code you've shared here looks correct. Only thing I can suggest is, if you're seeing the action come through in the debugger, your issue is either with the data/payload or logic within simpleReducer.
In this case you have it properly stripped down so I'd almost think this isn't actually the code you are running, it might be something in your build process?
In my React-native project in the HomeScreen, I get some values from AsyncStorage. After getting this value I compare it and take decision in which screen it will go next.
If the getValue is null then it will go the WelcomeScreen and if it is not null then it will go the HomeDrawer Screen.
Here I have provided the code-
import React from 'react';
import { StyleSheet, Text, View, AsyncStorage } from 'react-native';
import {StackNavigator} from 'react-navigation';
import WelcomeScreen from './WelcomeScreen';
import LoginScreen from './components/LoginScreen';
import NoteMeHome from './components/NoteMeHome';
import HomeDrawer from './HomeDrawer/HomeDrawer';
import SettingsScreen from './components/SettingsScreen';
class HomeScreen extends React.Component {
state = {
getValue: '',
}
async componentDidMount() {
const token = await AsyncStorage.getItem('toke');
this.setState({ getValue: token });
}
render() {
console.log('#ZZZ:', this.state.getValue);
if(this.state.getValue !== null) {
return (
<AppStackNavigator/>
);
} else {
return (
<AppStackNavigator2/>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
const AppStackNavigator = new StackNavigator({
HomeDrawer: {screen:HomeDrawer},
WelcomeScreen: {screen:WelcomeScreen},
LoginScreen: {screen:LoginScreen},
NoteMeHome: {screen:NoteMeHome},
SettingsScreen: {screen:SettingsScreen}
})
const AppStackNavigator2 = new StackNavigator({
WelcomeScreen: {screen:WelcomeScreen},
HomeDrawer: {screen:HomeDrawer},
LoginScreen: {screen:LoginScreen},
NoteMeHome: {screen:NoteMeHome},
SettingsScreen: {screen:SettingsScreen}
})
export default HomeScreen;
Now, after running this, if I get null value in the variable getValue , then it is showing the following warning-
Warning: Can't call setState(or forceUpdate) on an unmounted
component. this is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscription and asynchronous tasks in
the componentWillUnmount method.
So, how can I solve this warning issue?
I don't know whether it's a good practice or not. The problem was- my component was initializing with empty string and I was checking for null in render function. Initializing getvalue with null or checking for empty string in render would solve this issue.
So, the change I made in my code is -
state = {
getValue: ''
}
And it removes the warning.
A better solution would be to use the SwitchNavigator from react-navigation since your navigation stacks are identical and you only want to route to the first screen based on that token.
see example
import React from 'react';
import { StyleSheet, Text, View, AsyncStorage } from 'react-native';
import {StackNavigator, createSwitchNavigator} from 'react-navigation';
import WelcomeScreen from './WelcomeScreen';
import LoginScreen from './components/LoginScreen';
import NoteMeHome from './components/NoteMeHome';
import HomeDrawer from './HomeDrawer/HomeDrawer';
import SettingsScreen from './components/SettingsScreen';
const AppStackNavigator = new StackNavigator({
HomeDrawer: {screen:HomeDrawer},
LoginScreen: {screen:LoginScreen},
NoteMeHome: {screen:NoteMeHome},
SettingsScreen: {screen:SettingsScreen}
});
export default createAppContainer(createSwitchNavigator(
{
LaunchScreen,
WelcomeScreen,
AppStackNavigator,
},
{
initialRouteName: 'LaunchScreen',
}
));
class LaunchScreen extends React.Component {
constructor(props) {
super(props);
this._getToken();
}
// Fetch the token from storage then navigate to the appropriate place
_getToken = async () => {
const tok = await AsyncStorage.getItem('toke');
// This will switch to the Welcome screen or main AppStack. Then this launch
// screen will be unmounted and thrown away.
this.props.navigation.navigate(tok ? 'AppStackNavigator' : 'WelcomeScreen');
};
// Render any loading content that you like here
render() {
return (
<View>
{/*...*/}
</View>
);
}
}
I'm not sure how to describe what I'm trying to do with words so please take a look at the following code:
This is what causing me issues: this.fetchMessages()
import React, { Component } from 'react';
import { PushNotificationIOS, FlatList, TextInput, ActivityIndicator, ListView, Text, View, Image, TouchableWithoutFeedback, AsyncStorage } from 'react-native';
import { Actions } from 'react-native-router-flux';
import ConversationsItem from './ConversationsItem';
import { conversationFetch } from '../actions';
import { connect } from 'react-redux';
import { Divider } from 'react-native-elements'
import PushNotification from 'react-native-push-notification';
class Conversations extends Component {
componentDidMount() {
this.props.conversationFetch()
}
fetchMessages() {
this.props.conversationFetch()
}
render() {
PushNotification.configure({
onNotification: function(notification) {
PushNotification.getApplicationIconBadgeNumber((response) => {
PushNotification.setApplicationIconBadgeNumber(response + 1)
})
console.log( 'NOTIFICATION:', notification )
notification.finish(PushNotificationIOS.FetchResult.NoData);
this.fetchMessages()
}
});
if (!this.props.email) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
console.log(this.props.conversations)
return (
<View style={{flex: 1, backgroundColor: 'white'}}>
...
</View>
);
}
}
const mapStateToProps = (state) => {
console.log(state)
const { conversations } = state.conversation;
const { email } = state.conversation;
return { conversations, email };
};
export default connect(mapStateToProps, { conversationFetch })(Conversations);
When I call this.fetchMessages() inside PushNotification.configure({}), I get the following error message:
this.fetchMessages is not a function
I'm not sure if what I'm doing is possible but if so I'd really like to know how to make this work.
I tried adding .bind(this) and other ways around but got same error anyways.
Thanks for you help.
Functions declared with function keyword has their own this, so this inside onNotification function does not refer to the class.
Therefore use arrow function syntax, which will lexically resolve this and value of this inside will refer to class itself. So convert
onNotification: function(notification) {
to
onNotification: (notification) => {
So you have in fact tried binding the fetchMessages function in the constructor? Like such:
constructor(props) {
super(props)
this.fetchMessages = this.fetchMessages.bind(this);
}
You can also use an arrow function to bind your method to the class without calling the constructor like such:
() => this.fetchMessages()
I don't understand how I'm getting this error (pic below). In my LoginForm.js file, the onEmailChange(text) is giving me an unresolved function or method call to onEmailChange() error when I hover over it in my WebStorm IDE. In my index.js file, no error is being thrown anywhere.
I've looked around SO for this issue but it doesn't fully pertain to my problem.
I've tried File > Invalidate Caches/Restart but that didn't work.
Here's App.js:
import React, { Component } from 'react';
import {StyleSheet} from 'react-native';
import {Provider} from 'react-redux';
import {createStore} from 'redux';
import firebase from 'firebase';
import reducers from './reducers';
import LoginForm from './components/common/LoginForm';
class App extends Component {
render() {
return(
<Provider style={styles.c} store={createStore(reducers)}>
<LoginForm/>
</Provider>
);
}
}
const styles = StyleSheet.create({
c: {
flex: 1
}
});
export default App;
Here's LoginForm.js:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {emailChanged} from 'TorusTeensApp/src/actions';
import {Text, StyleSheet, KeyboardAvoidingView, TextInput, TouchableOpacity} from 'react-native';
class LoginForm extends Component {
render() {
onEmailChange(text)
{
this.props.emailChanged(text);
}
return(
<KeyboardAvoidingView style={styles.container}>
<TextInput
style={styles.userInput}
onsubmitediting={() => this.passwordInput.focus()}
returnKeyType={"next"}
placeholder={"Email"}
label={"Email"}
keyboardType={"email-address"}
autoCorrect={false}
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
/>
<TextInput
style={styles.userInput}
ref={(userInput) => this.passwordInput = userInput}
returnKeyType={"go"}
placeholder={"Password"}
label={"Password"}
secureTextEntry
/>
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>Create Account</Text>
</TouchableOpacity>
</KeyboardAvoidingView>
);
}
}
const styles = StyleSheet.create({
container: {
padding: 20 // creates a gap from the bottom
},
userInput: {
marginBottom: 20,
backgroundColor: '#9b42f4',
height: 40
},
buttonContainer: {
backgroundColor: '#41bbf4',
paddingVertical: 10,
marginBottom: 20
},
buttonText: {
textAlign: 'center',
color: '#FFFFFF'
}
});
const mapStateToProps = state => {
return {
email: state.auth.email
};
};
export default connect(mapStateToProps, null, {emailChanged}) (LoginForm);
Here's index.js:
import {EMAIL_CHANGED} from './types';
export const emailChanged = (text) => {
return {
type: 'EMAIL_CHANGED',
payload: text
};
};
export default emailChanged();
Your connect is miswired
connect(mapStateToProps, null, {emailChanged}) (LoginForm);
It should be something like:
connect(mapStateToProps,
(dispatch) => ({emailChanged: (text) => dispatch(emailChanged(text))})
)(LoginForm);
so that your action actually gets dispatched
and as spotted by emed in comment:
export default emailChanged;
without parentheses.
You defined your callback inside your render() method and not inside the class body. Do it like this:
class LoginForm extends Component {
onEmailChange(text) {
this.props.emailChanged(text);
}
render() {
return(...);
}
}
Also you shouldn't bind methods inside your render() method. Do it in the constructor of your Component:
class LoginForm extends Component {
constructor(props) {
super(props);
this.onEmailChange.bind(this);
}
onEmailChange(text) {
// do something
}
// other methods
}
Or if you use babel and ES6, you can define your callback with an arrow function, then it will be automatically bound:
class LoginForm extends Component {
onEmailChange = text => {
// do something
};
// other methods
}
See also the react docs about autobinding.
Also your call to connect seems incorrect. If you want to dispatch the action emailChanged it has to look like this:
const mapStateToProps = state => {
return {
email: state.auth.email
};
};
const mapDispatchToProps = dispatch => {
// this put a function emailChanged into your props that will dispatch the correct action
emailChanged: text => dispatch(emailChanged(text))
};
const LoginFormContainer = connect(mapStateToProps, mapDispatchToProps)(LoginForm);
export default LoginFormContainer;
The third argument to connect needs to be a function that knows how to merge the output of mapStateToProps, mapDispatchToProps, and ownProps all into one object that is then used as props for your connected component. I think you're trying to pass that action to the mapDispatchToProps argument, which is the second argument not the third. So, based on what I think you're doing, you probably wanna change your connect line to look like this.
export default connect(mapStateToProps, {emailChanged}) (LoginForm);
Then, export the function from your actions file not the output of calling that function.
export default emailChanged;
Notice I removed the parentheses so it's not being called.
Then make the callback function a method on your class and bind it in the constructor.
constuctor(props) {
super(props);
this.onEmailChange = this.onEmailChange.bind(this);
}
onEmailChange(text) {
this.props.emailChanged(text);
}
Then update onChangeText on that element.
onChangeText={this.onEmailChange}