Expo-built App keeps restarting after successful built - react-native

I have created a screen where user should scan a QR code before a few button is showed to them. The function is working fine in the development environment, however when I built the APK file and try to scan a QR code, suddenly the app crash and restart.
From what I've read so far, I think the problem may be due to the async function which prevent the qrscanned handler function to run. but I may be wrong and I'm trying not to break the code. I'd like to ask if any of you can point out where should I change in order to solve the problem. The following is the code for the screen.
import React, { useState, useEffect, useContext } from 'react';
import { Text, View, StyleSheet, Button, ImageBackground, Picker, Alert, ScrollView, FlatList } from 'react-native';
import { BarCodeScanner } from 'expo-barcode-scanner';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { Card } from 'react-native-paper';
import {AuthContext} from '../components/context';
const ScannerScreen = ({navigation}) => {
const { employeeNameDetails } = useContext(AuthContext);
var username = employeeNameDetails();
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
const [data, setData] = useState('Scan QR Code');
const [itemData, setItemData] = useState([]);
const [room, setRoom] = useState('');
const [roomName, setRoomName] = useState([]);
const [bilik, setBilik] = useState(false);
const updateRoom = () =>{
if(room != 'PILIH BILIK'){
var updateAPI = "https://example.com/api/setroom.php"
var header ={
'Accept': 'application/json',
'Content-type': 'application/json'
};
var itemdata ={
roomName: room,
asetID: data
};
fetch(
updateAPI,
{
method: 'POST',
headers: header,
body: JSON.stringify(itemdata)
}
)
.then((response)=>response.json())
.then((response)=>
{
if (response[0].isCorrect ==1){
Alert.alert(
"Berjaya","Bilik telah ditetapkan"
)}
else{
Alert.alert(
"Error","Tiada maklumat item dalam database"
)
}
}
)
.catch((error)=>{
alert("Error" + error);
})
}else{
Alert.alert(
"Error!","Sila pilih bilik."
)
}}
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
const handleBarCodeScanned = ({ data }) => {
setScanned(true);
setData(data);
var searchAPI = "https://example.com/api/searchitembyid.php";
var header = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
var itemdata = {
itemid: data
};
fetch(
searchAPI,
{
method: 'POST',
headers: header,
body: JSON.stringify(itemdata)
}
)
.then((response)=>response.json())
.then((responseJSON)=>{
setItemData(responseJSON)
});
var roomAPI = "https://example.com/api/getroom.php";
var roomheader = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
var roomdata = {
username: username
};
fetch(
roomAPI,
{
method: 'POST',
headers: roomheader,
body: JSON.stringify(roomdata)
}
)
.then((response)=>response.json())
.then((responseJSON)=>{
setRoomName(responseJSON)
});
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
const _renderItem = ({item, index}) =>{
return(
<View>
<Text style={styles.trackingNo}>{item.itemname}</Text>
<Text style={styles.trackingNo}>{item.nosiri}</Text>
</View>
)
}
return (
<ImageBackground
source={require('../assets/background-image.jpg')}
style={styles.image}>
<ScrollView style={styles.container}>
<View style={styles.top}>
<Text style={styles.title}>Scan Item</Text>
</View>
<View style={styles.barcodeSection}>
<Card>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={styles.barcode}
/></Card>
</View>
<View style={styles.bottomSection}>
<Card>
{scanned &&
<Button color="#fbf32f" title={'Scan Again'} onPress={() => {setScanned(false); setData('Scan QR Code')}} />}
{scanned &&
<View>
<FlatList
data={itemData}
renderItem={_renderItem}
keyExtractor={(item,index)=> index.toString()}
/>
<Text style={styles.btnText}>Set Room</Text>
<Picker
selectedValue={room}
style={styles.pickerInput}
onValueChange={(itemValue, itemIndex) => setRoom(itemValue)}>
{roomName.map((item, index) => {
return (< Picker.Item label={item.roomname} value={item.roomname} key={index} />);
})}
</Picker></View>
}
{scanned && <TouchableOpacity
style={styles.btn}
onPress={updateRoom}
><Text style={styles.btnText}>Tetapkan Bilik</Text></TouchableOpacity>
}
</Card></View>
</ScrollView></ImageBackground>
);
}
export default ScannerScreen;
Any suggestion or solution is greatly appreciated. Thanks guys.

Related

Expo React Native: BarcodeScanner.requestPermissionsAsync() doesnt apply a permission request

I'm trying to build a Barcode scanner component and I'm using expo-barcode-scanner.
the console.log works fine and I get the ASKING text.
the dialog window doesnt show up
here is my code:
import React, { useState, useEffect } from 'react';
import { Text,SafeAreaView, View, StyleSheet, Button } from 'react-native';
import { BarCodeScanner } from 'expo-barcode-scanner';
const ScannerComponent =()=> {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
const askForCameraPermission = () =>{
console.log('ASKING')
async () => {
const {status} = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === 'granted');
}
}
useEffect(() => {
askForCameraPermission();
}, []);
const handleBarCodeScanned = ({ type, data }) => {
setScanned(true);
alert(`Bar code with type ${type} and data ${data} has been scanned!`);
};
if (hasPermission === null) {
return (<View style={styles.container}><Text style={styles.text}>Requesting for camera permission</Text></View>);
}
if (hasPermission === false) {
return (<View style={styles.container}><Text style={styles.text}>No access to camera</Text></View>);
}
return (
<SafeAreaView style={styles.container}>
<Text value='hello' />
<View style={styles.scanner}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
</View>
{scanned && <Button title={'Tap to Scan Again'} onPress={() => setScanned(false)} />}
</SafeAreaView>
);
}
export default ScannerComponent

Render after fetching async data

I am fetching data in useEffect() and then modifying the object (modifying clients.unreadMessages based on what should render icon), which is later sent to the component to render. But this component does not render correctly always, the icon is sometimes missing. I think it's because data are modified after rendering.
ClientList
import Colors from '#helper/Colors';
import { useSelector } from '#redux/index';
import { HealthierLifeOption } from '#redux/types';
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';
import ClientItem from './ClientItem';
import {usePubNub} from "pubnub-react";
export type Client = {
id: number;
username: string;
individualPlanPaid: boolean;
healthierLifeOption?: HealthierLifeOption;
company?: {
id: number;
companyName: string;
};
mentor?: {
id: number;
username: string;
};
}
type Props = {
navigation: any;
clients: Client[];
isAdmin?: boolean;
isLoading: boolean;
onEndReached: Function;
fromAdminTab?: boolean
}
const ClientList = ({ navigation, clients, onEndReached, isLoading, isAdmin = false, fromAdminTab= false }: Props) => {
const resultCount = useSelector(state => state.user.menteesResultCount);
const [hasMoreResults, setHasMoreResults] = useState(true);
const userId = useSelector(state => state.user.id);
const pubnub = usePubNub();
useEffect(() => {
setHasMoreResults(resultCount !== clients?.length);
}, [resultCount, clients]);
useEffect(() => {
getUnreadMessagesProccess().then(r => console.log("aaaaaaaaa"));
}, []);
async function setGrant() {
return new Promise( async resolve => {
await pubnub.grant({
channels: [userId.toString() + '.*'],
ttl: 55,
read: true,
write: true,
update: true,
get: true,
}, response => resolve(response));
});
}
async function getMetadata() {
const options = {include: {customFields: true}};
return await pubnub.objects.getAllChannelMetadata(options);
}
function setChannelsAndTokens(channelMetadata, channels, tokens) {
channelMetadata.data.forEach((value, index) => {
tokens.push(value.custom.lastToken);
channels.push(value.id)
});
}
async function getUnreadedMessages(channels, tokens) {
return await pubnub.messageCounts({
channels: channels,
channelTimetokens: tokens,
});
}
async function getUnreadMessagesProccess() {
const tokens = ['1000'];
const channels = ['1234567'];
const auth = await setGrant();
const channelMetadata = await getMetadata();
const l = await setChannelsAndTokens(channelMetadata, channels, tokens);
const unread = await getUnreadedMessages(channels, tokens).then((res) => {
clients.forEach((value, index) => {
if (res.channels[value.id + '-' + userId + '-chat']) {
value.unreadMessages = res["channels"][value.id + '-' + userId + '-chat'];
} else {
value.unreadMessages = 0;
}
})
console.log(res);
});
return unread;
}
return (
<View>
<FlatList
keyExtractor={item => item.id.toString()}
data={clients}
onEndReached={() => hasMoreResults ? onEndReached() : null}
onEndReachedThreshold={0.4}
renderItem={item => (
<ClientItem
client={item.item}
isAdmin={isAdmin}
navigation={navigation}
fromAdminTab={fromAdminTab}
/>
)}
ListFooterComponent={isLoading
? () => (
<View style={{ padding: 20 }}>
<ActivityIndicator animating size="large" color={Colors.border_gray} />
</View>
)
: null
}
/>
</View>
);
}
export default ClientList;
ClientItem
import React, {useEffect, useState} from 'react';
import { Text, View, Image, TouchableOpacity } from 'react-native';
import Images from '#helper/Images';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
import Colors from '#helper/Colors';
import styles from '../styles';
import ClientModal from './ClientModal/ClientModal';
import { Client } from './ClientList';
import { HealthierLifeOption } from '#redux/types';
type Props = {
client: Client;
navigation: any;
isAdmin?: boolean;
fromAdminTab?: boolean
}
const ClientItem = ({ client, navigation, isAdmin = false, fromAdminTab= false }: Props) => {
const [showModal, setShowModal] = useState(false);
const [showIcon, setShowIcon] = useState(false)
console.log(client.unreadMessages)
useEffect(() =>{
if(client.unreadMessages>0)
setShowIcon(true);
},[client.unreadMessages]);
let clientIcon = Images.icon.logoIcon;
const handleContinueButton = () => {
if(!fromAdminTab) {
navigation.navigate('ChatFromClientsList', { selectedId: client.id, showBackButton: true });
}else {
setShowModal(!showModal)
}
};
return (
<View style={styles.client_item_container}>
<TouchableOpacity style={styles.client_content_container} onPress={handleContinueButton}
>
<View style={styles.image_container}>
<Image
style={styles.client_profile_img}
source={clientIcon}
resizeMode="contain"
resizeMethod="resize"
/>
</View>
<View style={styles.title_container}>
<Text style={styles.title} >{ client.username } </Text>
</View>
<View style={styles.dot_container}>
{showIcon && <FontAwesome5
name="comment-dots"
size={20}
color={Colors.red}
/> }
</View>
<View style={styles.hamburger_container} >
<FontAwesome5
name="bars"
size={30}
color={Colors.black}
onPress={() => setShowModal(!showModal)}
/>
</View>
<View>
<ClientModal
isVisible={showModal}
onCollapse={(value: boolean) => setShowModal(value)}
closeModal={(value: boolean) => setShowModal(value)}
client={client}
navigation={navigation}
isAdmin={isAdmin}
/>
</View>
</TouchableOpacity>
</View>
);
};
export default ClientItem;
This code does not render correctly always:
{showIcon && <FontAwesome5
name="comment-dots"
size={20}
color={Colors.red}
/> }
You should not calculate the value in renderItem.
Why you don’t calc the value outside of renderItem in pass it in
useEffect(() =>{
if(client.unreadMessages>0)
setShowIcon(true);
},[client.unreadMessages]);
Do something like:
renderItem={item => (
<ClientItem
client={item.item}
isAdmin={isAdmin}
navigation={navigation}
fromAdminTab={fromAdminTab}
showIcon={item.item.unreadMessages>0}
/>
)}
By the way renderItem should not be a anonymous function.

Why can't I navigate to my Screen using onPress={() => navigation.navigate('SaveProfileImage, { profileImage })}?

I'm developing an app where I have a screen called Add, other called Save, that I navigate between them using onPress={() => navigation.navigate('Save', { image })} inside a button, so I tried to do the same on another screen called profile that should navigate to the screen SaveProfileImage, but when I try this, I get the error TypeError: undefined is not an object (evaluating 'props.route.params') and I can't understand what can be so wrong in my codes:
Here is the Profile codes:
import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text, Image, FlatList, Button } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import firebase from 'firebase';
require('firebase/firestore');
import { connect } from 'react-redux';
function Profile({ navigation }, props) {
const [userPosts, setUserPosts] = useState([]);
const [user, setUser] = useState(null);
const [hasGalleryPermission, setHasGalleryPermission] = useState(null);
const [image, setImage] = useState(null);
useEffect(() => {
const { currentUser, posts } = props;
if (props.route.params.uid === firebase.auth().currentUser.uid) {
setUser(currentUser)
setUserPosts(posts)
}
else {
firebase.firestore()
.collection('users')
.doc(props.route.params.uid)
.get()
.then((snapshot) => {
if (snapshot.exists) {
setUser(snapshot.data());
}
else {
console.log('does not exist')
}
})
firebase.firestore()
.collection('posts')
.doc(props.route.params.uid)
.collection('userPosts')
.orderBy('creation', 'desc')
.get()
.then((snapshot) => {
let posts = snapshot.docs.map(doc => {
const data = doc.data();
const id = doc.id;
return { id, ...data }
})
setUserPosts(posts)
});
};
(async () => {
const galleryStatus = await ImagePicker.requestMediaLibraryPermissionsAsync();
setHasGalleryPermission(galleryStatus.status === 'granted');
})();
}, [props.route.params.uid, props.following]);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
if (!result.cancelled) {
setImage(result.uri);
};
};
if (hasGalleryPermission === false) {
return <View />;
};
if (hasGalleryPermission === false) {
return <Text>No access to gallery</Text>;
};
const onLogout = () => {
firebase.auth().signOut();
};
if (user === null) {
return <View />
}
return (
<View style={styles.container}>
{image && <Image source={{ uri: image }}
style={{ flex: 1 }} />}
<Image source={{ uri: props.route.params.profileImage }} />
<View style={styles.containerInfo}>
<Text>{user.name}</Text>
<Text>{user.state}</Text>
<Text>{user.city}</Text>
<Text>{user.email}</Text>
{props.route.params.uid !== firebase.auth().currentUser.uid}
<Button title='Pick Image From Gallery' onPress={() => pickImage()} />
<Button title='Save'
onPress={() => navigation.navigate('SaveProfileImage',
{ profileImage })} />
</View>
<View style={styles.container}>
<FlatList
numColumns={3}
horizontal={false}
data={userPosts}
renderItem={({ item }) => (
<View
style={styles.containerImage}>
<Image
style={styles.image}
source={{ uri: item.downloadURL }}
/>
</View>
)}
/>
</View>
<Button
title='Logout'
onPress={() => onLogout()}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
containerInfo: {
margin: 20
},
containerImage: {
flex: 1 / 3
},
image: {
flex: 1,
aspectRatio: 1 / 1
}
})
const mapStateToProps = (store) => ({
currentUser: store.userState.currentUser,
posts: store.userState.posts,
following: store.userState.following
})
export default connect(mapStateToProps, null)(Profile);
The app.js has a navigation container to control these screens:
<NavigationContainer >
<Stack.Navigator initialRouteName='Main'>
<Stack.Screen name='Main' component={MainScreen} />
<Stack.Screen name='Add' component={AddScreen}
navigation={this.props.navigation}/>
<Stack.Screen name='Save' component={SaveScreen}
navigation={this.props.navigation}/>
<Stack.Screen name='SaveProfileImage' component={SaveProfileImageScreen}
navigation={this.props.navigation}/>
<Stack.Screen name='Comment' component={CommentScreen}
navigation={this.props.navigation}/> </Stack.Navigator>
<NavigationContainer >
I can't understand why I can navigate from the AddScreen to the Save Screen, but I can't do the same between the profileScreen and the SaveProfileImage.
The save is this one:
import React, { useState } from 'react'
import { View, TextInput, Image, Button } from 'react-native'
import firebase from 'firebase'
require('firebase/firestore')
require('firebase/firebase-storage')
export default function Save(props) {
const [caption, setCaption] = useState('')
const uploadImage = async () => {
const uri = props.route.params.image;
const childPath =
`post/${firebase.auth().currentUser.uid}/${Math.random().toString(36)}`;
const response = await fetch(uri);
const blob = await response.blob();
const task = firebase
.storage()
.ref()
.child(childPath)
.put(blob);
const taskProgress = snapshot => {
console.log(`transferred: ${snapshot.bytesTransferred}`)
};
const taskCompleted = () => {
task.snapshot.ref.getDownloadURL().then((snapshot) => {
savePostData(snapshot);
})
};
const taskError = snapshot => {
console.log(snapshot)
};
task.on('state_changed', taskProgress, taskError, taskCompleted);
};
const savePostData = (downloadURL) => {
firebase.firestore()
.collection('posts')
.doc(firebase.auth().currentUser.uid)
.collection('userPosts')
.add({
downloadURL,
caption,
likesCount: 0,
estate: '',
city: '',
creation: firebase.firestore.FieldValue.serverTimestamp()
}).then((function () {
props.navigation.popToTop()
}))
};
return (
<View style={{ flex: 1 }}>
<Image source={{ uri: props.route.params.image }} />
<TextInput
placeholder='Write a Caption . . .'
onChangeText={(caption) => setCaption(caption)}
/>
<Button title='Save' onPress={() => uploadImage()} />
</View>
);
};
The SaveProfileScreen is this other one:
import React, { useState } from 'react'
import { View, TextInput, Image, Button } from 'react-native'
import firebase from 'firebase'
require('firebase/firestore')
require('firebase/firebase-storage')
export default function ProfileImage(props) {
const [caption, setCaption] = useState('')
const uploadImage = async () => {
const uri = props.route.params.image;
const childPath =
`profileImage/${firebase.auth().currentUser.uid}/
${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const task = firebase
.storage()
.ref()
.child(childPath)
.put(blob);
const taskProgress = snapshot => {
console.log(`transferred: ${snapshot.bytesTransferred}`)
};
const taskError = snapshot => {
console.log(snapshot)
};
const taskCompleted = () => {
task.snapshot.ref.getDownloadURL().then((snapshot) => {
savePictureData(snapshot);
})
};
task.on('state_changed', taskProgress, taskError, taskCompleted);
};
return (
<View style={{ flex: 1 }}>
<Image source={{ uri: props.route.params.profileImage }} />
<Button title='Save' onPress={() => uploadImage()} />
</View>
);
};

Why react-native doesn't render all content when 2 render methods included in it?

I have some content to be rendered conditionally and some fixed content i.e. footer. I dont want to render footer every time when state changes, hence I've added two methods renderContent() and renderFooter to be called in render() method.
Below code, doesn't render both methods.
'use strict';
import React, { Component } from 'react';
import { Alert, FlatList, View, StyleSheet, Text, Linking, Button } from 'react-native';
import { AsyncStorage } from 'react-native';
import getEnvVars from '../environment';
const { apiUrl } = getEnvVars();
import Moment from 'moment';
import { Ionicons } from '#expo/vector-icons';
import FootBar from '../screens/FootBar';
import { LinesLoader } from 'react-native-indicator';
export default class SubscriptionsToEnd extends Component {
static navigationOptions = ({ navigation }) => {
const { state } = navigation;
return {
title: `${state.params && state.params.title ? state.params.title : 'Subscriptions Due'}`,
};
};
constructor(props) {
super(props);
this.state = {
isLoaded: false,
dataSource: [],
title: 'Subscriptions Due'
};
}
componentDidMount() {
this._getAllCustomers();
}
_getAllCustomers() {
let url;
if (this.state.title === 'Subscriptions Due') {
url = apiUrl + "/customersWithSubscriptionNearToEnd/";
this.props.navigation.setParams({ title: 'Subscriptions Due' })
}
if (this.state.title === 'Customers') {
url = apiUrl + "/customers/";
this.props.navigation.setParams({ title: 'Customers' })
}
this.setState({ isLoaded: false })
try {
AsyncStorage.multiGet(['role', 'jwt']).then((data) => {
let role = data[0][1];
let jwt = data[1][1];
if (role === 'Admin') {
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'jwt': jwt
},
}).then(res => res.json())
.then(
(result) => {
if (result.message != 'Unauthorized user!' && this.state.title === 'Customers') {
this.setState({
isLoaded: true,
dataSource: result,
title: 'Subscriptions Due'
});
} else if (result.message != 'Unauthorized user!' && this.state.title === 'Subscriptions Due') {
this.setState({
isLoaded: true,
dataSource: result,
title: 'Customers'
});
} else if (result.message === 'Unauthorized user!') {
this.props.navigation.navigate('Login');
}
},
(error) => {
console.log(error);
this.setState({
isLoaded: true
});
this.props.navigation.navigate('Login');
}
)
}
})
} catch (error) {
console.log('Error at getting token \n' + error)
}
}
GetGridViewItem(id) {
Alert.alert(id);
}
_logOutAsync = async () => {
await AsyncStorage.clear();
this.props.navigation.navigate('Auth');
};
_addCustomer() {
// TBD
}
renderContent() {
if (!this.state.isLoaded) {
return (
<View style={styles.loader}>
<LinesLoader color='#1d91a5' barWidth={5} barHeight={60} barNumber={5} betweenSpace={5} />
</View>
)
}
if (this.state.isLoaded) {
return (
<View style={styles.container}>
<View style={styles.grid}>
<FlatList
data={this.state.dataSource}
renderItem={({ item }) =>
<View style={styles.GridViewContainer}>
<Text style={styles.GridViewTextLayout}>
<Text onPress={this.GetGridViewItem.bind(this, item._id)}>
<Text style={styles.Name}>{item.firstname}</Text> <Text style={styles.Name}>{item.lastname}</Text> {"\n"}
<Text>{Moment(item.till_date).format('Do MMM YYYY')} </Text>{"\n\n"}
</Text>
<Text onPress={() => { Linking.openURL('tel:+44' + item.mobile); }}><Ionicons name="md-phone-portrait" size={22} color="#1d91a5" /> {item.mobile}</Text> {"\n\n"}
<Text><Ionicons name="md-mail" size={22} color="#1d91a5" />{item.email}</Text>
</Text>
</View>}
numColumns={2}
keyExtractor={(item, index) => index.toString()}
/>
</View >
</View>
)
};
}
renderFooter() {
return (
<View style={styles.buttonsContainer}>
<View style={styles.button}>
<Button color='#1d91a5' title={this.state.title} onPress={this._getAllCustomers.bind(this)} />
</View>
<View style={styles.button}>
<Button color='#1d91a5' title="+Customer" onPress={this._addCustomer.bind(this)} />
</View>
<View style={styles.button}>
<Button color='#1d91a5' title="Logout" onPress={this._logOutAsync.bind(this)} />
</View>
</View>
);
}
render() {
return (
this.renderContent(),
this.renderFooter()
);
}
}
Above code only renders this.renderFooter() method. If I swap methods in render(), it renders this.renderContent().
Can someone please tell me why it is failing to render both?
I was doing it wrong. Main render() method should be like:
render() {
return (
<View style={styles.wrapper}>
{this.renderContent()}
{this.renderFooter()}
</View>
);
}
It looks like you figured it out just before I could post my answer.
The return function can only return one view. Your 2 functions each return a view. So wrapping both functions in a single view solves the problem.

maximum call stack size exceed

Guys i have a main component like this
export default class mobile extends Component {
constructor(props){
super(props);
this.state = {
loading: true
}
}
async componentWillMount(){
let defaultKey = 'login';
const token = await AsyncStorage.getItem('token');
if (token){
console.log('token');
const storedUser = await AsyncStorage.getItem('user');
const payload = JSON.parse(storedUser);
store.dispatch({
type: 'LOGIN_SUCCESS',
payload
})
defaultKey = 'home';
}
const config = await AsyncStorage.getItem('config');
console.log(config);
if ((process.env.NODE_ENV === "development") && !config) {
defaultKey = 'setup';
}
this.setState({loading: false});
Actions[defaultKey]();
}
render() {
return(
<Provider store={ store }>
<Router>
<Scene key="root">
<Scene key="setup"
title="Configuration"
component={ ConfigComponent } />
<Scene key="login"
title="Login"
component={ Login } />
<Scene key="home"
title="Home"
component={ Home } />
<Scene key="signup"
title="Signup"
component={ Signup }/>
</Scene>
</Router>
</Provider>
)
}
};
and another config component which basically takes TextInput value and after successful submission it navigates to login page with
Action.login() where login is key name of login scene
I am not sure when i call Action.login() after successful submission inside fetch post success callback.it throws me error "maximum call size exceeded".My simulator freezes and after many seconds i can see my login view.I tried to call Action.login() outside of fetch call, but same issue. Please look at handleFormSubmit() function.
import React, { Component } from 'react';
import update from 'react-addons-update';
import { View, Text, TextInput, Button, ScrollView, TouchableHighlight, Platform } from 'react-native';
import { Actions, ActionConst } from 'react-native-router-flux';
import { ProgressIndicator } from '../common-scenes/progress';
import { Styles } from '../../app/common/styles';
import { AUTH } from '../../app/common/enums';
import { alert } from '../../app/common/alert';
import { asyncStorage } from '../../app/common/helper';
import { InputBox } from './inputComponent';
export default class ConfigComponent extends Component{
constructor(){
super();
this.state = {
formObject: {
APIURL: 'http://localhost:3000',
MongodbURL: 'mongodb://localhost:27017/react_app',
FacebookAppID: '1780990305515917',
FacebookClientSecret: 'cc7341b671731df7efe44add63b1c79e',
FacebookDisplayName: 'Wish',
Platform: Platform.OS
},
errors:{
APIError: '',
FacebookError: '',
MongodbError: ''
},
test:{
API: false,
Facebook: false,
MongoDB: false
},
processFacebook: false,
processMongoDb: false,
processAPI: false
}
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.apiHandler = this.apiHandler.bind(this);
this.facebookHandler = this.facebookHandler.bind(this);
this.mongodbHandler = this.mongodbHandler.bind(this);
this.updateState = this.updateState.bind(this);
};
handleFormSubmit() {
if (this.state.test.Facebook) {
fetch(AUTH.CONFIG, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(this.state.formObject)
})
.then((response) => response.json())
.then((responseData) => {
asyncStorage('config', this.state.formObject)
.then((result) =>{
Actions.login({type: ActionConst.PUSH});
alert(responseData.message);
});
});
}
};
mongodbHandler(){
this.setState({processMongoDb: false})
var errors;
if (!this.state.formObject.MongodbURL){
errors = update(this.state.errors, {
'MongodbError':{
$set: 'Enter your mongodb connection string'
}
});
}else{
errors = update(this.state.errors, {
'MongodbError':{
$set: ''
}
});
}
this.setState({errors})
if (this.state.formObject.MongodbURL){
this.setState({processMongoDb: true})
fetch('http://localhost:3000/auth/mongodb/test',{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
MongodbURL: this.state.formObject.MongodbURL
})
})
.then((response) => response.json())
.then((responseData) =>{
let test = Object.assign({}, this.state.test, { MongoDB: true})
this.setState({test})
})
.catch((err) =>{
let test = Object.assign({}, this.state.test, { MongoDB: false})
this.setState({test})
})
}
}
facebookHandler(){
var errors;
this.setState({processFacebook: false})
if (!this.state.formObject.FacebookAppID || !this.state.formObject.FacebookDisplayName ){
errors = update(this.state.errors, {
'FacebookError':{
$set: 'Enter both facebook app id and facebook display name'
}
});
}else{
errors = update(this.state.errors, {
'FacebookError':{
$set: ''
}
})
}
this.setState({errors})
if (this.state.formObject.FacebookAppID
&& this.state.formObject.FacebookDisplayName
&& this.state.formObject.FacebookClientSecret){
this.setState({processFacebook: true})
fetch(AUTH.GRAPH,{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
FacebookAppID: this.state.formObject.FacebookAppID,
FacebookDisplayName: this.state.formObject.FacebookDisplayName,
FacebookClientSecret: this.state.formObject.FacebookClientSecret
})
})
.then((result) => result.json())
.then((responseData) => {
let test = Object.assign({}, this.state.test, { Facebook: true});
this.setState({test});
})
.catch((error) => {
let test = Object.assign({}, this.state.test, { Facebook: false});
this.setState({test});
})
}
};
apiHandler() {
let { APIURL } = this.state.formObject;
if(APIURL) {
this.setState({processAPI: true});
fetch(APIURL, {
method: 'HEAD'
})
.then(() => {
let test = Object.assign({}, this.state.test, { API: true });
this.setState({test});
})
.catch((error) => {
let test = Object.assign({}, this.state.test, { API: false });
this.setState({test});
})
}
};
updateState(id, text) {
let newValues = update(this.state.formObject, {
[id]: {
$set: text
}
})
this.setState({formObject: newValues})
};
render(){
return(
<ScrollView style={Styles.container}>
<View style={Styles.innerView}>
<InputBox id="APIURL" placeholder="API URL" title="API URL" updateState={this.updateState}/>
{(this.state.test.API && this.state.processAPI) && <Text style={{color: 'green'}}>Test passed</Text>}
{(!this.state.test.API && this.state.processAPI) && <Text style={{color: 'red'}}>Test Failed</Text>}
<TouchableHighlight style={Styles.button} onPress={this.apiHandler}>
<Text style={Styles.buttonText}>Test API</Text>
</TouchableHighlight>
</View>
<View style={Styles.innerView}>
<InputBox id="MongodbURL" placeholder="Mongodb URL" title="Mongodb Configuration" updateState={this.updateState}/>
<Text style={{color: 'red'}}>{this.state.errors.MongodbError}</Text>
{(this.state.test.MongoDB && this.state.processMongoDb) && <Text style={{color: 'green'}}>Test passed</Text>}
{(!this.state.test.MongoDB && this.state.processMongoDb) && <Text style={{color: 'red'}}>Test Failed</Text>}
<TouchableHighlight style={Styles.button} onPress={this.mongodbHandler}>
<Text style={Styles.buttonText}>Test Mongodb</Text>
</TouchableHighlight>
</View>
<View style={Styles.innerView}>
<InputBox id="FacebookAppID" placeholder="Facebook Client ID" title="Facebook Configuration" updateState={this.updateState} />
<InputBox id="FacebookClientSecret" placeholder="Facebook Client Secret" updateState={this.updateState} />
<InputBox id="FacebookDisplayName" placeholder="Facebook Display Name" updateState={this.updateState} />
<Text style={{color: 'red'}}>{this.state.errors.FacebookError}</Text>
{(this.state.test.Facebook && this.state.processFacebook) && <Text style={{color: 'green'}}>Test passed</Text>}
{(!this.state.test.Facebook && this.state.processFacebook) && <Text style={{color: 'red'}}>Test Failed</Text>}
<TouchableHighlight style={Styles.button} onPress={this.facebookHandler}>
<Text style={Styles.buttonText}>Test facebook</Text>
</TouchableHighlight>
</View>
<View>
{this.state.test.MongoDB && this.state.test.Facebook && <TouchableHighlight style={Styles.button} onPress={this.handleFormSubmit} underlayColor='#99d9f4'>
<Text style={Styles.buttonText}>Submit</Text>
</TouchableHighlight> }
</View>
</ScrollView>
)
}
}
This error is usually encountered when you enter an infinite loop. I believe you are calling this.setState({...}); in a method that triggers a re-render.