React-native stack navigation not showing right screen - react-native

all, I am having a problem that my stack navigation is not showing any screen.
I have 'signup' and 'login' screens, and I build a stack navigator(which is the startStack) try to navigate between those two screens. But it doesn't work. when a user first come(if she hasn't logged in before), the 'login' screen should be shown.
Please help me out , I really appreciate , thank you.
this is the App.js
import React, { Component } from "react";
import "react-native-gesture-handler";
import { StyleSheet, Text, View } from "react-native";
import Login from "./screens/login";
import Signup from "./screens/signup";
import MyOrders from "./screens/myOrders";
import firebase from "firebase";
import StartNavigator from "./routes/startStack";
class App extends Component {
state = {
loggedIn: null,
};
componentDidMount() {
var firebaseConfig = {
apiKey: "AIzaSyASR4GAXSRGsiDhOTF_UsdqHzqHYcHPk_U",
authDomain: "onlineorderportal-68a8f.firebaseapp.com",
databaseURL: "https://onlineorderportal-68a8f.firebaseio.com",
projectId: "onlineorderportal-68a8f",
storageBucket: "onlineorderportal-68a8f.appspot.com",
messagingSenderId: "658149255251",
appId: "1:658149255251:web:37940844cdc5403e173ea6",
measurementId: "G-KGQ1F2F3WE",
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
} else {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ loggedIn: true });
} else {
this.setState({ loggedIn: false });
}
});
}
}
renderContent = () => {
switch (this.state.loggedIn) {
case false:
console.log("false case login");
// return <Login />;
// return <Signup />;
return <StartNavigator />;
case true:
console.log("true case Navigator");
return <MyOrders />;
}
};
render() {
return (
<View style={styles.container}>
{this.renderContent()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#FFFFFF",
alignItems: "center",
justifyContent: "center",
},
});
export default App;
This is the startStack.js
import React from "react";
import { createStackNavigator } from "#react-navigation/stack";
import { NavigationContainer } from "#react-navigation/native";
import Login from "../screens/login";
import Signup from "../screens/signup";
const Stack = createStackNavigator();
function StartNavigator() {
console.log("enter Start");
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Login">
<Stack.Screen name="Signup" component={Signup} />
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default StartNavigator;
When I try to return 'Login' or 'Signup' separately, it correctly show the pages separately.
Thank you so much !!!

You have created StartNavigator, which has only two screens 'Login' and 'Singup'. And you have set initialRouteName as 'Login'. So your 'App' component never called. So all the code that you written on App component never run. To work this add App component in your StartNavigator and set initialRouteName to app like below :
import React from "react";
import { createStackNavigator } from "#react-navigation/stack";
import { NavigationContainer } from "#react-navigation/native";
import Login from "../screens/login";
import Signup from "../screens/signup";
import App from "../app/path"; // import App here
const Stack = createStackNavigator();
function StartNavigator() {
console.log("enter Start");
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Login">
<Stack.Screen name="App" component={App} /> // add app component
<Stack.Screen name="Signup" component={Signup} />
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default StartNavigator;

Did you check conditions after receive data from the server?
what is the value of 'loggedIn'?
I think that the value of 'loggedIn' is not correct or your switch case is not working.
use console.log before doing any condition for sure that the value of 'loggedIn' is correct.

Related

Google SignIn with expo: signInWithGoogleAsync not being invoked

Im following this tutorial https://github.com/nathvarun/Expo-Google-Login-Firebase and below is my code. However the signInWithGoogleAsync() function is not getting invoked it seems. Clicking the button does nothing. Could it be because of stack Navigator?
There is no issue with firebase as email login is working
Below is my LoginScreen.js
import { useNavigation } from '#react-navigation/core'
import React, { useEffect, useState, Component } from 'react'
import { Button, KeyboardAvoidingView, StyleSheet, Text,TextInput, TouchableOpacity, View } from 'react-native'
import { auth } from '../firebase'
import * as GoogleSignIn from 'expo-google-sign-in';
class LoginScreen extends Component {
signInWithGoogleAsync = async() => {
try {
const result = await Google.logInAsync({
behavior:'web',
iosClientId: 'lorem ipsum',
scopes: ['profile', 'email'],
});
if (result.type === 'success') {
return result.accessToken;
} else {
return { cancelled: true };
}
} catch (e) {
return { error: true };
}
};
render(){
return (
<KeyboardAvoidingView
style={styles.container}
behavior="padding"
>
<View style={styles.inputContainer}>
<Button
title='sign'
onPress={()=>this.signInWithGoogleAsync()} />
</View>
</KeyboardAvoidingView>
);
}
}
export default LoginScreen
here's the App.js:
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import LoginScreen from './screens/LoginScreen';
import HomeScreen from './screens/HomeScreen';
import * as firebase from "firebase/compat";
const Stack = createNativeStackNavigator();
export default class App extends React.Component {
render(){
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen options={{ headerShown: false }} name="Login" component={LoginScreen} />
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
registerRootComponent(App);
You are importing everythin as GoogleSignIn and then below doing await Google.logInAsync so you'd have to change that to await GoogleSignIn.loadInAsync. 9th line
Also, look at the docs, it doesn't look like logInAsync is a function https://docs.expo.dev/versions/latest/sdk/google-sign-in/
expo-google-sign-in has been now deprecated. You'll have to use expo-auth-session

React Native React Navigation screen does not redirect after login with AsyncStorage

So I built a login system with React Native, React Navigation and AsyncStorage. If the user clicks on a button he gets logged in and the value of the AsyncStorage Key "#loginuser" gets refreshed. Now I expected
that the screen automatically gets refreshed, but I have to close the App and start it again - This is not optimal.
(I also saw React-Native/React navigation redirection after login with redux post, but it is very old)
App.js
import React from 'react';
import { Text, View, StyleSheet, Button, TouchableOpacity, TextInput } from 'react-native';
import DefaultStackNavigation from './components/Navigation/Navigation';
const App = () => {
return(
<View>
<DefaultStackNavigation />
</View>
)
}
export default App;
Navigation.js
import React, {useEffect, useState} from 'react';
//React Native
import { Text, View, StyleSheet} from 'react-native';
//Screens
import HomeScreen from '../HomeScreen/HomeScreen'
import AddScreen from "../AddScreen/AddScreen";
import NotificationScreen from "../NotificationScreen/NotificationScreen";
import MenuScreen from "../MenuScreen/MenuScreen";
import SearchScreen from "../SearchScreen/SearchScreen";
import PostJobScreen from "../PostJobScreen/PostJobScreen";
import JobOfferScreen from "../JobOfferScreen/JobOfferScreen";
import ProfileScreen from "../ProfileScreen/ProfileScreen";
import NoneLoggedinScreen from "../NoneLoggedinScreen/NoneLoggedinScreen"
import SignupModal from "../NoneLoggedinScreen/SignupModal"
//React Navigation
import { createStackNavigator } from "#react-navigation/stack";
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
//Third Party
import AsyncStorage from '#react-native-async-storage/async-storage';
const Tab = createBottomTabNavigator();
//Tab bar
const HomeTabs = () => {
return (
<Tab.Navigator tabBarOptions={{style:{height: 50}}, {showIcon: true}, {showLabel: false}} >
<Tab.Screen name="Home" component={HomeScreen}/>
<Tab.Screen name="SearchScreen" component={SearchJobStack}/>
<Tab.Screen name="AddScreen" component={AddScreen}/>
<Tab.Screen name="NotificationScreen" component={NotificationScreen} />}}/>
<Tab.Screen name="MenuScreen" component={MenuScreen}/>}}/>
</Tab.Navigator>
);
}
const Stack = createStackNavigator();
const STORAGE_KEY = '#loginStatus'
const DefaultStackNavigation = () => {
const [loginStatus, setLoginStatus] = useState()
const readData = async () => {
try {
const isLoggedIn = JSON.parse(await AsyncStorage.getItem(STORAGE_KEY))
console.log(isLoggedIn)
if (isLoggedIn !== null) {
setLoginStatus(isLoggedIn)
}
} catch (e) {
alert('Failed to fetch the data from storage')
}
}
readData()
return loginStatus ? (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} independent={false}>
<Stack.Screen name="HomeTabs" component={HomeTabs} />
<Stack.Screen name="PostJobScreen" component={PostJobScreen} />
<Stack.Screen name="ProfileScreen" component={ProfileScreen}/>
</Stack.Navigator>
</NavigationContainer>
) : (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} independent={false}>
<Stack.Screen name="NoneLoggedinScreen" component={NoneLoggedinScreen} />
<Stack.Screen name="SignupModal" component={SignupModal} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default DefaultStackNavigation;
NoneLoggedinScreen.js
import React, { useState, useEffect } from 'react';
import { Text, View, Button} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
const STORAGE_KEY = '#loginStatus'
const SignupModal = () => {
const [loginStatus, setLoginStatus] = useState(false)
const saveData = async (parmLoginStatus) => {
try {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(parmLoginStatus))
alert('Data successfully saved -> Logged In')
console.log(loginStatus)
} catch (e) {
alert('Failed to save the data to the storage')
}
}
const onSubmitLogin = () => {
setLoginStatus(true)
saveData(true)
}
return(
<View>
<Text>Login Page. Press the button to log in and stay logged in</Text>
<Button title="Log in" onPress={() => onSubmitLogin()}/>
</View>
);
};
const styles = StyleSheet.create({
App: {
flex: 1,
backgroundColor: "white"
},
});
export default SignupModal;
Now I was expecting the page to reload and I can use the app. Unfortunately it doesn't. The data is saved and the login status is set to true, but I have to restart the app to use it. The perfidious thing is, if I write the AsyncStorage login logic from the NoneLoggedinScreen.js file into the App.js file, the app works fine -> However, this is not an alternative for me, because the general structure of the app is, I think, built relatively sensibly.
Also when the user tries to be redirected manually (by button) with navigation.navigate("HomeTabs") after logging in doesn't work, and I get an error that Home doesn't exist, which is also not understandable, because the navigation has actually been set to Logged in now. Has anyone ever had this problem?
This are my dependencies by the way
"dependencies": {<br>
"#react-native-async-storage/async-storage": "^1.15.1",<br>
"#react-native-community/masked-view": "^0.1.10",<br>
"#react-navigation/bottom-tabs": "^5.11.7",<br>
"#react-navigation/native": "^5.9.2",<br>
"#react-navigation/stack": "^5.14.2",<br>
"react": "16.13.1",<br>
"react-native": "0.63.4",<br>
"react-native-gesture-handler": "^1.10.1",<br>
"react-native-reanimated": "^1.13.2",<br>
"react-native-safe-area-context": "^3.1.9",<br>
"react-native-screens": "^2.17.1",<br>
}
In your authentication strategy, there is a hidden assumption that when you save data using AsyncStorage in NoneLoggedInScreen, the data will immediately be read using the readData function in DefaultStackNavigation. The reason why this does not happen is that readData is only called when DefaultStackNavigation renders/re-renders. This will not happen when one of its children sets some data in local storage.
There are many ways to fix this. Using redux with redux-persist or another state-management and persistence setup, or a purpose-built context-based solution (see eg. this Kent C. Dodds article on Authentication in React Applications).
The second, minor issue that you mention:
navigation.navigate("HomeTabs") after logging in doesn't work, and I get an error that Home doesn't exist
does make sense, and the reason behind it is that you conditionally render two different NavigationContainers. They don't know about each others routes, so you can't navigate between them.
To fix this, you should render one NavigationContainer and conditionally render one of the Stack.Navigators as its child.
To manage authentication flow in a smooth way we should make two seperate navigators for the navigation. I find this workflow the best as of now. It works perfectly and is clean.
So what I usually do in my Projects that I create two navigators AuthNavigator.js and AppNavigator.js and then use conditional rendering.
So what you can do is create a folder called navigation where your App.js is located. Then inside navigation folder create two files called AppNavigator.js and AuthNavigator.js.
Your AuthNavigator.js should look like this
import React from "react";
import NoneLoggedinScreen from "../NoneLoggedinScreen/NoneLoggedinScreen";
import SignupModal from "../NoneLoggedinScreen/SignupModal";
import { createStackNavigator } from "#react-navigation/stack";
const Stack = createStackNavigator();
const AuthNavigator = () => {
return (
<Stack.Navigator screenOptions={{ headerShown: false }} independent={false}>
<Stack.Screen name="NoneLoggedinScreen" component={NoneLoggedinScreen} />
<Stack.Screen name="SignupModal" component={SignupModal} />
</Stack.Navigator>
);
};
export default AuthNavigator;
Your AppNavigator.js should look like this
import React from "react";
import HomeScreen from "../HomeScreen/HomeScreen";
import AddScreen from "../AddScreen/AddScreen";
import NotificationScreen from "../NotificationScreen/NotificationScreen";
import MenuScreen from "../MenuScreen/MenuScreen";
import PostJobScreen from "../PostJobScreen/PostJobScreen";
import ProfileScreen from "../ProfileScreen/ProfileScreen";
import { createStackNavigator } from "#react-navigation/stack";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
const Tab = createBottomTabNavigator();
//Tab bar
const HomeTabs = () => {
return (
<Tab.Navigator
tabBarOptions={
({ style: { height: 50 } }, { showIcon: true }, { showLabel: false })
}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="SearchScreen" component={SearchJobStack} />
<Tab.Screen name="AddScreen" component={AddScreen} />
<Tab.Screen name="NotificationScreen" component={NotificationScreen} />
<Tab.Screen name="MenuScreen" component={MenuScreen} />
</Tab.Navigator>
);
};
const Stack = createStackNavigator();
const AppNavigator = () => {
return (
<Stack.Navigator screenOptions={{ headerShown: false }} independent={false}>
<Stack.Screen name="HomeTabs" component={HomeTabs} />
<Stack.Screen name="PostJobScreen" component={PostJobScreen} />
<Stack.Screen name="ProfileScreen" component={ProfileScreen} />
</Stack.Navigator>
);
};
export default AppNavigator;
And your App.js should look like this
import React, { useState, useEffect } from 'react';
import { NavigationContainer } from '#react-navigation/native';
import AuthStorage from './auth/storage';
import AppNavigator from './navigation/AppNavigator';
import AuthNavigator from './navigation/AuthNavigator';
const App = () => {
const [loginStatus, setLoginStatus] = useState(false);
// I am using useEffect hook but I would prefer using `AppLoading` to restore token if it exists
useEffect(() => {
readData();
}, []);
const readData = async () => {
try {
const isLoggedIn = JSON.parse(await AuthStorage.getItem());
console.log(isLoggedIn);
if (isLoggedIn !== null) {
setLoginStatus(isLoggedIn);
}
} catch (e) {
alert('Failed to fetch the data from storage');
}
};
return (
<NavigationContainer>
{loginStatus ? <AppNavigator /> : <AuthNavigator />}
</NavigationContainer>
);
};
export default App;
Create a folder called auth where your App.js is located. Inside that create a file called storage.js. Now inside storage.js paste this code
import AsyncStorage from '#react-native-async-storage/async-storage';
const AuthToken = '#loginStatus';
const storeItem = async (value) => {
try {
await AsyncStorage.setItem(AuthToken, JSON.stringify(value));
return true;
} catch (e) {
return false;
}
};
const getItem = async () => {
try {
const jsonValue = await AsyncStorage.getItem(AuthToken);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
return null;
}
};
export default { storeItem, getItem };

How to pass "this.state" of a Component to a Screen in React Navigation 5

I am new to React Native and I just started learning ReactNavigation.
The app that I'm working at is meant to keep track of players and points while you're playing a board game. Here's the code of App.js:
import * as React from 'react';
import {Component} from 'react';
import { StyleSheet, Text, View, Button, Alert } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { create StackNavigator } from '#react-navigation/stack';
import { Player } from './player';
const Stack = createStackNavigator();
export default class BoardGameApp extends Component{
constructor(props){
super(props);
this.state={ playerTable:[] }
}
render(){
let numOfPlayers = 4
const totNumOfTerritories = 48
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
}
function HomeScreen(){
return(
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Add a new Player" onPress={
() => this.setState({ playerTable: [
...this.state.playerTable,
{ playerName: "whatever"},
]
})}>
</Button>
/* this is commented
{this.state.playerTable.map(
function(player) {
return <Player territories={totNumOfTerritories / numOfPlayers} />;
}
)} here the comment ends */
</View>
);
}
So basically what should happen is that whenever I press the Button, an object consisting of {playerName: "whatever"} should be added to the PlayerTable[] array present in the state of BoardGameApp, and then what I subsequently do is that I map over this array and render a Player component for each object {playerName: "whatever"} present in the array. When all of this was happening inside the App component without using screens everything was fine, but of course it looked awful.
Once I started using screens however, when I press the Button on my phone it gives me the error undefined is not an object (evaluating '_this2.setState') because I guess it doesn't recognize what "this" is.
I am learning ReactNative by following the Cs50 course 2018 where "ScreenProps" were still available. I made some research and I think that my answer should be somewhere around Initial Params, React Contextor Redux even if I still don't know these concepts, however whenever I try to look up on the docs or on Youtube, it just gives me way more complicated stuff than just passing a state from an object to a screen. So this is why I ultimately decided to ask this question here.
Working Example: Expo Snack
Console Output:
You can solve it using Context API:
import React, { Component, createContext, useContext } from 'react';
import { StyleSheet, Text, View, Button, Alert } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
// import { Player } from './player';
const Stack = createStackNavigator();
const ParentContext = createContext();
export default class BoardGameApp extends Component {
constructor(props) {
super(props);
this.state = { playerTable: [] };
}
addPlayer = (player) => {
console.log('data added :', [...this.state.playerTable, player]);
this.setState({
playerTable: [...this.state.playerTable, player],
});
};
render() {
let numOfPlayers = 4;
const totNumOfTerritories = 48;
return (
<ParentContext.Provider value={this.addPlayer}>
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
</ParentContext.Provider>
);
}
}
function HomeScreen() {
const addPlayer = useContext(ParentContext);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="Add a new Player"
onPress={() => {
addPlayer({ playerName: 'whatever' });
}}></Button>
</View>
);
}
const styles = StyleSheet.create({});
Old solution which was not the right way according to comment given by #satya so avoid it but I am keeping it in answer for future ref:
import * as React from 'react';
import { Component } from 'react';
import { StyleSheet, Text, View, Button, Alert } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
// import { Player } from './player';
const Stack = createStackNavigator();
export default class BoardGameApp extends Component {
constructor(props) {
super(props);
this.state = { playerTable: [] };
}
addPlayer = (player) => {
console.log('hi:', [...this.state.playerTable, player]);
this.setState({
playerTable: [...this.state.playerTable, player],
});
};
render() {
let numOfPlayers = 4;
const totNumOfTerritories = 48;
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={() => <HomeScreen onClick={this.addPlayer} />}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
}
function HomeScreen({ onClick }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="Add a new Player"
onPress={() => {
onClick({ playerName: 'whatever' });
}}></Button>
</View>
);
}
const styles = StyleSheet.create({});

at using Expo, After splash screen, Blink(Flash) with white screen

I made Expo app with react native navigation 5.
please refer my solved question(same install situation).
but After splash on FIRST launching, White screen is blink(Flashed : about 0.5 sec). Especially, At Dark theme, I can find this bug easily.
This is App.tsx
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import useCachedResources from './hooks/useCachedResources';
import useColorScheme from './hooks/useColorScheme';
import Navigation from './navigation';
export default function App() {
const isLoadingComplete = useCachedResources();
const colorScheme = useColorScheme();
if (!isLoadingComplete) {
return null;
} else {
return (
<SafeAreaProvider>
<Navigation colorScheme={colorScheme} />
<StatusBar style='light'/>
</SafeAreaProvider>
);
}
}
and this is index.tsx at nvigation
import { RootStackParamList } from '../types';
import BottomTabNavigator from './BottomTabNavigator';
import LinkingConfiguration from './LinkingConfiguration';
export default function Navigation({ colorScheme }: { colorScheme: ColorSchemeName }) {
return (
<NavigationContainer
linking={LinkingConfiguration}
theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<RootNavigator />
</NavigationContainer>
);
}
const Stack = createStackNavigator<RootStackParamList>();
function RootNavigator() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Root" component={BottomTabNavigator} />
<Stack.Screen name="NotFound" component={NotFoundScreen} options={{ title: 'Oops!' }} />
</Stack.Navigator>
);
}
At Hook, useCachedResources.ts
import { MaterialIcons } from '#expo/vector-icons';
import * as Font from 'expo-font';
import * as SplashScreen from 'expo-splash-screen';
import * as React from 'react';
export default function useCachedResources() {
const [isLoadingComplete, setLoadingComplete] = React.useState(false);
React.useEffect(() => {
async function loadResourcesAndDataAsync() {
try {
SplashScreen.preventAutoHideAsync();
await Font.loadAsync({
...MaterialIcons.font,
'space-mono': require('../assets/fonts/SpaceMono-Regular.ttf'),
});
} catch (e) {
console.warn(e);
} finally {
setLoadingComplete(true);
SplashScreen.hideAsync();
}
}
loadResourcesAndDataAsync();
}, []);
return isLoadingComplete;
}
and useColorScheme.ts
import { Appearance, ColorSchemeName } from 'react-native';
import { useEffect, useRef, useState } from 'react';
export default function useColorScheme(delay = 500): NonNullable<ColorSchemeName> {
const [colorScheme, setColorScheme] = useState(Appearance.getColorScheme());
let timeout = useRef<NodeJS.Timeout | null>(null).current;
useEffect(() => {
Appearance.addChangeListener(onColorSchemeChange);
return () => {
resetCurrentTimeout();
Appearance.removeChangeListener(onColorSchemeChange);
};
}, []);
function onColorSchemeChange(preferences: Appearance.AppearancePreferences) {
resetCurrentTimeout();
timeout = setTimeout(() => {
setColorScheme(preferences.colorScheme);
}, delay);
}
function resetCurrentTimeout() {
if (timeout) {
clearTimeout(timeout);
}
}
return colorScheme as NonNullable<ColorSchemeName>;
}
How to solve this bug? Please give me your hand.
I just solved this issue by specifying backgroundColor in my app.json to match the background color of the splash screen.
Expo Docs for backgroundColor
(string) - The background color for your app, behind any of your React views. This is also known as the root view background color.
6 character long hex color string, for example, '#000000'. Default is white: '#ffffff'.
Try to use SplashScreen.preventAutoHideAsync() in your App.js before its declaration.
For example:
SplashScreen.preventAutoHideAsync();
export default function App() {
...
Don't forget to delete the SplashScreen.preventAutoHideAsync() call in your loadResourcesAndDataAsync() function. Keep it only in the App.js file.
I hope this help you. Worked for me.
Just got a breakthrough on this after few days so what you will modify in your index.ts file
your code
function RootNavigator() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Root" component={BottomTabNavigator} />
<Stack.Screen name="NotFound" component={NotFoundScreen} options={{ title:
'Oops!' }} />
</Stack.Navigator>
);
}
what it should be :
function RootNavigator() {
return (
<Stack.Navigator initialRouteName="Root" screenOptions={{ headerShown: false
}}>
<Stack.Screen name="Root" component={BottomTabNavigator} />
<Stack.Screen name="NotFound" component={NotFoundScreen} options={{ title:
'Oops!' }} />
</Stack.Navigator>
);
}
by adding initialRouteName your problem should get solved
their other reason also makes sure that your import {useState,useEffect } or other hooks from react not react.development, you will face that if you published your app on the expo or generated API or apk

switchNavigator with react-navigation 5

With react-navigation 4, I was able to import and use switchNavigator from "react-navigation" package.
import {
createAppContainer,
createSwitchNavigator,
createStackNavigator
} from "react-navigation";
import MainTabNavigator from "./MainTabNavigator";
import LoginScreen from "../screens/LoginScreen";
import AuthLoadingScreen from "../screens/AuthLoadingScreen";
export default createAppContainer(
createSwitchNavigator(
{
App: MainTabNavigator,
Auth: AuthLoadingScreen,
Login: createStackNavigator({ Login: LoginScreen })
},
{
initialRouteName: "Auth"
}
)
);
With react-navigation 5, I don't see the createSwitchNavigator in the package anymore. The documentation isn't helpful either. Is the use now not recommended? My use case is to show login screen before user is logged in and switch to the app after user logs in. React-navigation has given an example of authentication flow but is it possible to use switchNavigator - which seems much simpler.
The switchNavigator was removed because you can do the exact same stuff now with the help of rendering components conditionally.
import React from 'react';
import {useSelector} from "react-redux";
import {NavigationContainer} from "#react-navigation/native";
import { AuthNavigator, MyCustomNavigator } from "./MyCustomNavigator";
const AppNavigator = props => {
const isAuth = useSelector(state => !!state.auth.access_token);
return (
<NavigationContainer>
{ isAuth && <MyCustomNavigator/>}
{ !isAuth && <AuthNavigator/>}
</NavigationContainer>
);
};
export default AppNavigator;
The lines inside the NavigationContainer fully replace the old switch navigator.
I had also used SwitchNavigator of Navigator 4 then after migrating other pages to Navigator 5, i tried to move authentication part to Navigator 5. I could not achieve SwitchNavigator functionality using Navigator 5. Then decided to use "Compatibility layer" provided in Navigation API 5. https://reactnavigation.org/docs/5.x/compatibility
Hope below code will useful for you.
import { createStackNavigator } from '#react-navigation/stack'
import { NavigationContainer } from '#react-navigation/native';
import { createSwitchNavigator } from "#react-navigation/compat";
import { createCompatNavigatorFactory } from '#react-navigation/compat'
const AppStack = createCompatNavigatorFactory(createStackNavigator)(
{ screen: Home },
{headerMode:'none'}
);
const AuthStack = createCompatNavigatorFactory(createStackNavigator)({ screen:Login });
const SwitchNavigator= createSwitchNavigator(
{
Starter: AuthValidation,
App: AppStack,
Auth: AuthStack
},
{
initialRouteName:'Starter'
}
);
export default function App (){
return(
<NavigationContainer>
<SwitchNavigator/>
</NavigationContainer>
);
}
Here AuthValidation validate for token and depending on value it navigate to "Login" or "Home" Page
_checkAuthetication = async() =>{
const isUserAuthenticated= await AsyncStorage.getItem("isAuthenticated");
this.props.navigation.navigate(isUserAuthenticated ? 'App':'Auth');
}
Hey there is no switch navigator in react navigation 5, however you can do this or something on the same lines:
import React, { useEffect } from 'react'
import { StyleSheet, Text, View, ActivityIndicator } from 'react-native'
import { NavigationContainer } from "#react-navigation/native";
import BottomTabsNavigator from './BottomTabsNavigator'
import AccountNavigator from './AccountNavigator'
import firebase from '../api/config'
const SwitchNavigator = ({navigation}) => {
useEffect(() => {
firebase.auth().onAuthStateChanged(user => {
navigation.navigate(user ? "BottomTabsNavigator" : "AccountNavigator")
})
}, [])
return (
<View style={styles.container}>
<Text>Loading...</Text>
<ActivityIndicator size="large" color="#e9446a"></ActivityIndicator>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})
export default SwitchNavigator
and then a Stack Navigator :
import React from 'react'
import { createStackNavigator } from '#react-navigation/stack'
import BottomTabsNavigator from './BottomTabsNavigator'
import AccountNavigator from './AccountNavigator'
import SwitchNavigator from './SwitchNavigator'
import { NavigationContainer } from "#react-navigation/native";
const StackApp = createStackNavigator()
export default function Stack() {
return (
<NavigationContainer>
<StackApp.Navigator initialRouteName='Loading' headerMode='none'>
<StackApp.Screen name='Loading' component={SwitchNavigator} />
<StackApp.Screen name='AccountNavigator' component={AccountNavigator}/>
<StackApp.Screen name='BottomTabsNavigator' component={BottomTabsNavigator}/>
</StackApp.Navigator>
</NavigationContainer>
)
}
and then import the Stack navigator into your app.js file like this:
export default App = () => ( <Stack /> )
This is w.r.t to above query
[Then how could we later navigate from a "LoginScreen" (inside
AuthNavigator ) to "HomeScreen" (inside MyCustomNavigator )? – TalESid
Apr 7 at 8:33 ]
const AuthNavigator = () => {
return(
<AuthStack.Navigator>
<Stack.Screen
name="Login"
component={Login}
options={{ headerShown: false }}
/>
<Stack.Screen
name="SignUp"
component={SignUp}
options={{ headerShown: false }}
/>
</AuthStack.Navigator>
);
}
const MyCustomNavigator = () => {
return(
<AppStack.Navigator>
<Stack.Screen
name="Home"
component={Home}
options={{ headerShown: false }}
/>
<Stack.Screen
name="ListScreen"
component={ListScreen}
options={{ headerShown: false }}
/>
<Stack.Screen
name="Settings"
component={Settings}
options={{ headerShown: false }}
/>
</AppStack.Navigator>
);
}
const AppNavigator = (props) => {
const isAuth = useSelector((state) => !!state.auth.access_token);
return (
<NavigationContainer>
{isAuth && <MyCustomNavigator />}
{!isAuth && <AuthNavigator />}
</NavigationContainer>
);
};
export default AppNavigator;
There isnt a switch navigator in react-navigation v5 or v6. however, i found switch very easy, and i find stack very difficult to use, so i continue to use react-navigation v4.2.0 or 4.4.1, so i can continue using switch