For the life of me, I can't figure it out. All it shows is spinning without end and i am confused on the order of the life cycle happening. Basically, it goes to login or home screen and it works correctly on emulator but not on real device. I am on react 16.8.6 and react-native 0.60.5 environment.
I am getting started with RN and my debugging tools are not great. But for now just used Alert to see and the logic that was supposed to redirect to login/home screen is never reached. The Alerts shown are in the following order:
BS
mount2
render
mount1
My code is below: if the token exists, load home screen. else load auth screen is what I wanted to achieve but for now the line:
this.props.navigation.navigate(!goToLogin ? 'App' : 'Auth');
is never reached and so, spins a lot. Any help?
import React, {Component} from 'react';
import {StatusBar, View, Alert} from 'react-native';
import {
getUserToken,
loggedInToAssociation,
extractToken,
} from '../shared/loggedinUser';
import {setLanguage} from '../shared/localization';
import {appOptions} from '../config';
import Spinner from '../components/Spinner';
export default class AuthLoadingScreen extends Component {
constructor() {
super();
this.state = {
languageLoaded: false
};
}
componentDidMount() {
Alert.alert("mount1","oumnt1") // shown
loggedInToAssociation()
.then(details => {
// details is an array now
setLanguage(details['language']);
this.setState({languageLoaded: true});
Alert.alert("mount2","oumnt2") // SHOWN
})
.catch(err => {
setLanguage(appOptions.defaultLanguage);
this.setState({languageLoaded: true});
Alert.alert("mount3","oumnt3")
});
}
// Fetch the token from storage then navigate to our appropriate place
_bootstrapAsync = async () => {
const userToken = await getUserToken();
Alert.alert("bs","bs") // SHOWN
const tokenInfo = extractToken(userToken, 'both');
let goToLogin = true; // force user to go to the login page
if (tokenInfo.length == 2) {
goToLogin = false;
}
Alert.alert("bs2","bs2") // NEVER SHOWN
this.props.navigation.navigate(!goToLogin ? 'App' : 'Auth');
};
// Render any loading content that you like here
render() {
if (this.state.languageLoaded){
this._bootstrapAsync().then(s=>{
console.log(s)
}).catch(e=>{
console.log(e)
})
}
return (
<View>
<Spinner />
<StatusBar barStyle="default" />
</View>
);
}
}
did you check your debug console when running on device? There might be an unhandled promise rejection. The promise didn't go through but nowhere to handle the catch (consider try-catch scenario for this context).
It might be having a problem with this method.
extractToken(userToken, 'both')
Related
I'm developing a cross platform mobile App.
I'm testing my code on a Android Studio emulator (google pixel 5, api level 30) and i'm using expo version : ~43.0.2 and expo-location version : ~13.0.4
I've already asked for the location permission, and it works. But when I call the following code i log "there" but never "here":
console.log("there")
const userLocation = await Location.getCurrentPositionAsync()
console.log("here")
Indeed, the function Location.getCurrentPositionAsync() seems locked
A similar issue has been know in the past according to these links:
React Native Expo-Location returns Location service unavailable during initial useEffect
https://forums.expo.dev/t/getcurrentpositionasync-doesnt-return-any-value-infinity-loading/23643
But it's also the code in the Expo doc :
https://snack.expo.dev/#charliecruzan/expo-map-and-location-example
. Bellow the entire app class :
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import {Text, TextInput, Pressable, View, Alert} from 'react-native';
import * as Location from "expo-location"
export default class App extends React.Component{
state = {
errorMessage: "",
location: {}
}
getAddress(){
return this.state.address
}
_getLocation = async ()=>{
const {status} = await Location.requestForegroundPermissionsAsync();
if (status !== "granted"){
console.log("PERMISSION LACK!")
this.setState({
errorMessage:"PERMISSION NOT GRANTED"
});
}
console.log("there")
const userLocation = await Location.getCurrentPositionAsync();
console.log("here")
console.log(JSON.stringify(userLocation))
this.setState({
location: userLocation
})
}
render(){
this._getLocation()
return (
<View>
<Text>Salut</Text>
</View>
);
}
}
What did i missed?
Add accuracy and maximumAge in parameters with Location.Accuracy.Highest and 10000 respectively as shown below:
JavaScript:
const userLocation = await Location.getCurrentPositionAsync({accuracy: Location.Accuracy.Highest, maximumAge: 10000});
The solution came from How to use getCurrentPositionAsync
function in expo-location | Tabnine.
As explained in this reddit post, the location service only works in emulators if you are logged into a google account.
I want to navigate the user to another screen in react native project after native app widget click in android. I was able to catch event using native event emitter in my MainView.js and there i changed state of one of my component and it got changed but UI is not getting rendered after this state change. It is showing blank screen and there is not error on the console. Thanks in advance for any help!!
export default class MainView extends React.Component {
constructor(props) {
super(props);
this.state = {text: 'Hi, This is main screen for app widget!!!'};
}
componentDidMount() {
const eventEmitter = new NativeEventEmitter();
this.listener = eventEmitter.addListener('MyCustomEvent', (event) => {
console.log('MyCustomEvent -->', event);
console.log('MyCustomEvent ArticleId -->', event.ArticleId);
if (event.ArticleId === data.articleId) {
console.log('data ArticleId true', data.articleId);
//navigation.push('Article Details', data);
this.setState({
text: data.articleDes,
});
// setText(data.articleDes);
console.log('text -->', this.state.text);
} else {
// setText('No such article found.');
console.log('text -->', this.state.text);
}
});
}
componentWillUnmount() {
this.eventListener.remove(); //Removes the listener
}
render() {
return (
<View style={{flex: 1}}>
<Text>{this.state.text}</Text>
<Button
title="click"
onPress={() => this.props.navigation.push('Article Details', data)}
/>
</View>
);
}
}
CustomActivity source code which is launched from appwidget click. From this activity's oncreate, I'm emitting events to react-native main view.
int articleId = 0;
if (getIntent() != null) {
articleId = getIntent().getIntExtra("articleId", 0);
Log.e("articleid", "" + articleId);
}
// Put data to map
WritableMap payload = Arguments.createMap();
payload.putInt("ArticleId", articleId);
// Emitting event from java code
ReactContext context = getReactNativeHost().getReactInstanceManager().getCurrentReactContext();
if ( context != null && context.hasActiveCatalystInstance()) {
Log.e("react context", "not null");
(getReactNativeHost().getReactInstanceManager().getCurrentReactContext())
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("MyCustomEvent", payload);
}
That is not how to use NativeEventEmitter. You need to initialise the NativeEventEmitter with the native module you are emitting events from:
import { NativeEventEmitter, NativeModules } from 'react-native';
const { myNativeModule } = NativeModules;
componentDidMount() {
...
const eventEmitter = new NativeEventEmitter(myNativeModule);
this.eventListener = eventEmitter.addListener('myEvent', (event) => {
console.log(event.eventProperty) // "someValue"
});
...
}
componentWillUnmount() {
this.eventListener.remove(); //Removes the listener
}
Read more about NativeModules here: https://reactnative.dev/docs/native-modules-android
This sound familiar with an issue I am experiencing on IOS. The code is similar, but I cannot guarantee that the underlying structure in Android works in the same way. Anyways, I am sending an event message from IOS-Native (written in swift in xCode) to React-native file using the NativeEventEmitter. After the initial render, the value just wont update, and as I understand this issue is not limited to this type of Event. After some googling I found out that everything you read from state inside that event-callback has a reference to only the first render, and will not update on future renders.
Solution; use useRef so you keep a reference to the the updated value. useRef keeps the value across renders and event-callbacks. This is not something I have found out myself, please look at https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559 and React useState hook event handler using initial state for, they are the one that deserves the credit.
I have no idea what is causing this bug in my react native app. I'm using version 5 of the React Navigation library.
It randomly crashes the app sometimes. Google searching hasn't helped me understand what this is. It's very selective though which is a good(or a bad) thing.
So what does this mean and what could be causing it?
if(navigation.canGoBack()) {
navigation.dispatch(StackActions.pop(1));
}
see https://github.com/react-navigation/react-navigation/issues/7814#issuecomment-599921016
Did you try this?
this.props.navigation.goBack()
It means you tried to pop a view where there was nothing to pop. It might mean there's a bug in your app because, generally, you shouldn't be popping a view when there isn't any to pop.
But it can also be part of intentional design where you have insufficient knowledge of the current navigation state, but need to make sure at least one pop is done (similar to clearing a flag variable even if it might not be set in the first place, in which case it would be a no-op). If that's the case, then you can disable this development-level warning:
const temp = console.error;
console.error = () => {};
navigation.pop();
console.error = temp;
Error Cause: goBack() or pop() is getting called multiple times. Sometimes onPress event gets called many times. You can check by adding console.log().
How to Solve: you need to throttle the onPress function.
Example:
import React, { PureComponent } from 'react'
import { Text, View } from 'react-native'
import { throttle } from 'lodash'
export default class Test extends PureComponent {
constructor(props) {
super(props)
this.state = {
}
this.onPress = throttle(this.onPress, 500, {trailing: false})
}
onPress = () => {
console.log("going back")
this.props.navigation.pop();
//this.props.navigation.goBack();
}
render() {
return (
<View>
<Text>Hello World!</Text>
</View>
)
}
}
you need to check there can go back or not by canGoBack method like this
import { StackActions } from '#react-navigation/native';
if(this.refs.navigation.canGoBack())
{
this.refs.navigation.dispatch(StackActions.pop(1));
// this.refs.navigation.dispatch(StackActions.popToTop());
}
I'm making authentication in an app, and I'm kind of stuck. I have 2 different navigations. One shows if the user is logged in and another one if not. Basically, a Sign in screen. It's working fine if I change the value manually upon the start. But I can't find a way to change a state when a user signs in, for example. Even though the value in auth module changes, it doesn't update in App.js So how can I update the App.js's state from Sign in screen, for example?
import React, { Component } from 'react';
import { AppRegistry, Platform, StyleSheet, Text, View } from 'react-native';
import DrawerNavigator from './components/DrawerNavigator'
import SignedOutNavigator from './components/SignedOutNavigator'
import auth from './auth'
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props)
this.state = {
isLoggedIn: auth.isLoggedIn
}
}
render() {
return (
(this.state.isLoggedIn) ? <DrawerNavigator /> : <SignedOutNavigator />
);
}
}
AppRegistry.registerComponent('App', () => App)
and my auth module, which is very simple
import { AsyncStorage } from 'react-native';
// try to read from a local file
let api_key
let isLoggedIn = false
function save_user_settings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
isLoggedIn = true
});
}
module.exports.save_user_settings = save_user_settings
module.exports.api_key = api_key
module.exports.isLoggedIn = isLoggedIn
First off, there are loads of ways to approach this problem. Because of this I'm going to try explain to you why what you have now isn't working.
The reason this is happening is because when you assign auth.isLoggedIn to your isLoggedIn state, you are assigning the value once, kind of as a copy. It's not a reference that is stored.
In addition to this, remember, React state is generally only updated with setState(), and that is never being called here, so your state will not update.
The way I would approach this problem without bringing in elements like Redux, which is overkill for this problem by itself, is to look into building an authentication higher order component which handles all the authentication logic and wraps your entire application. From there you can control if you should render the children, or do a redirect.
Auth Component
componentDidMount() {
this._saveUserSettings(settings);
}
_saveUserSettings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
this.setState({isLoggedIn: true});
});
}
render() {
const { isLoggedIn } = this.state;
return isLoggedIn ? this.props.children : null;
}
App.js
render() {
<AuthComponent>
//the rest of authenticated app goes here
</AuthComponent>
}
Here's a really quick, incomplete example. But it should showcase to you how you may want to lay your authentication out. You'll also want to consider error handling and such, however.
I am trying to track screen names on react-native-firebase in conjunction with react-navigation.
Here is my code.
const tracker = firebase.analytics()
function getCurrentRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getCurrentRouteName(route);
}
return route.routeName;
}
export default class AppNavigation extends Component {
render() {
StatusBar.setBarStyle('light-content');
return (
<MainScreenNavigator
onNavigationStateChange={(prevState, currentState) => {
const currentScreen = getCurrentRouteName(currentState);
const prevScreen = getCurrentRouteName(prevState);
if (prevScreen !== currentScreen) {
// the line below uses the Google Analytics tracker
// change the tracker here to use other Mobile analytics SDK.
tracker.setCurrentScreen(currentScreen);
}
}}
/>
);
}
}
When I console log the screen names, they appear as desired. However, I'm not seeing the results in Firebase console. When I filter screen by name it just says (not set). Am I doing something wrong in my code? I am importing firebase from 'react-native-firebase' as well.
The code above is solid. It turns out you have to wait a half a day or so before data is populated. Not sure if I missed that in the docs. If you're using react-navigation and firebase, this code works!