How to navigate to other screen after the whole app has loaded? - react-native

Good day! Currently learning Nav 5 and so far this is what I have tried. I want to navigate to other screens depending on the result of the Firebase login check.
But what happens is the app renders 3times in the Splash screen, what I want is the 3rd or the final data. How can I know when the app is done re-rendering stuff.
This is what I have tried and I am fairly new to React navigation hopefully somebody could point me on how to do this properly. Thank you!
APP.JS
useEffect(() => {
const subscriber = firebase.auth().onAuthStateChanged(user => {
let logged = false
if (user) {
logged = true
firebase.firestore().collection('users')
.doc(user.uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
return
}
const user = firestoreDocument.data()
console.log("subs data", user)
setUser(user)
})
.catch(error => {
alert(error)
})
} else {
setUser(null)
logged = false
}
setIsLoggedIn(logged)
setIsLogIncheckDone(true)
})
return subscriber // unsubscribe on unmount
}, [])
const getSomething = async () => {
const data = {
isLoggedIn, user, isLogInCheckDone
}
return data
}
if (!isLogInCheckDone) {
return <SplashScreen />
}
return (
<AppContext.Provider value={{ GetSomething: getSomething }}>
<ThemeContextProvider>
<ThemeContextConsumer>
{context =>
(
<NavigationContainer>
<Stack.Navigator theme={context.theme ? "dark" : "light"} >
<Stack.Screen name={"Splash"} component={SplashScreen} options={{ headerShown: false }} />
<Stack.Screen name={"Common"} component={CommonStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Auth"} component={AuthStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Dashboard"} component={DashboardStack} options={{ headerShown: false }} />
<Stack.Screen name={"Verify"} component={VerificationStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Wizard"} component={ProfileWizardStack} options={{ headerShown: false }} />
</Stack.Navigator>
<Toast ref={(ref) => Toast.setRef(ref)} />
</NavigationContainer>
)
}
</ThemeContextConsumer>
</ThemeContextProvider>
</AppContext.Provider>
)
}
SPLASHSCREEN.JS
export default function SplashScreen({ navigation, route }) {
const appContext = useContext(AppContext)
useEffect(() => {
const newData = appContext?.GetSomething()
console.log("Splashy", newData)
//TODO Navigate here
}, [appContext])
return (
<View style={styles.container}>
<LottieView source={newSource} autoPlay loop />
</View>
)
}

You can use the logged state in your app.js to render what you want. Try something like this:
return (
<NavigationContainer >
{logged ? <Screens you want to render if the user is logged /> : <SplashScreen />}
</NavigationContainer>
);

Related

asyncstorage not working in build mode in react native

in debug mode asyncstorage working perfectly fine but when build apk with this command
gradlew assembleRelease -x bundleReleaseJsAndAssets
the apk build perfectly but i want to open it show me the error appstop in my phone
any help regarding this will be very appriciated
Try this.
import AsyncStorage from '#react-native-async-storage/async-storage';
export default function App()
{
const [aldreadyLaunched, setaldreadyLaunched] = useState(true)
useEffect(() => {
AsyncStorage.getItem("alreadyLaunched").then((value) => {
if (value == "false") {
let parsedValue = JSON.parse(value)
setaldreadyLaunched(parsedValue)
}
})
},[])
return (<>
display introductory screen when the already launched state is true and when the false display login or directly main screen
</>)
}
Try this .....
const [loading, setLoading] = useState(true);
const [isFirstTimeLoad, setIsFirstTimeLoad] = useState(false);
const checkForFirstTimeLoaded = async () => {
const result = await AsyncStorage.getItem('isFirstTimeOpen');
console.log('result:', result);
if (result == null) {
setIsFirstTimeLoad(true);
setLoading(false);
} else {
setIsFirstTimeLoad(false);
setLoading(false);
}
};
if (loading)
return (
<View style={{flex:1,justifyContent:'center',alignItems:'center'}}>
<ActivityIndicator size={'large'} color={'black'}/>
</View>
);
if (isFirstTimeLoad)
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="OnboardingScreen"
screenOptions={{
headerShown: false,
// header: () => <MainHeader />,
}}>
<Stack.Screen name="OnboardingScreen" component={OnBoardingScreen} />
<Stack.Screen name="login" component={Login} />
<Stack.Screen name="home" component={Home} />
<Stack.Screen name="register" component={Register} />
<Stack.Screen name="mobileverify" component={MobileVerify} />
<Stack.Screen name="listscreen" component={ListData} />
</Stack.Navigator>
</NavigationContainer>
);
if (!isFirstTimeLoad) return <Login />;

How can i notify to App for what reload the app to show another screen?

I am in login screen i pressed the button login so
that button have this action
store.dispatch({
type: 'login',
payload: {
name: email.value
},
})
on the reducers change the value
export default function (state, action) {
switch (action.type) {
case 'login':
return {
...state,
user: action.payload,
isLogged:true
}
default:
return newState
}
}
now i have to notify to App.js for the new change because that is depend to show the stack.screen
export default function App() {
return (
<Provider store={store} theme={theme}>
<Tabs store={store} />
</Provider>
)
}
How can i notify to App for what reload the app to show another screen?
but never my component is updated, i was trying to update my component using useEffect but not
function Tabs ({ user }) {
const {
data: { isLogged },
} = useSelector((state) => state)
useEffect(() => {
console.log(1234546)
}, [isLogged, user])
return (
<Stack.Navigator >
{ !store.getState().data.isLogged ?
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/> :
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>}
</Stack.Navigator>
)
When working with States Or Redux We don't have to tell the app about when to re-render the JSX code.
It will be re-renders automatically whenever the change occurs in redux or state.
But, We must have to be assured that we are not mutating the data in our state or redux, We always have to assign a new object to state or redux.
You can try the below changes in your code,
function Tabs ({ user }) {
const { isLogged } = useSelector((state) => state?.data)
return (
<Stack.Navigator >
{
isLogged
?
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/>
:
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>
}
</Stack.Navigator>
)
}
App navigator need to subscribe isLogged state then rerender accordingly.
import { useEffect } from "react";
import { useSelector } from "react-redux";
const App = () => {
const isLogged = useSelector((state) => state.isLogged);
// Track authentication state
useEffect(() => {}, [isLogged]);
return (
<Stack.Navigator>
{!isLogged ? (
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/>
) : (
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>
)}
</Stack.Navigator>
);
};

React Navigation Authentication Flow: The action 'NAVIGATE' with payload {"name":"HomeScreen"} was not handled by any navigator

A beginner at React Native here, trying to combine this doc with AWS Amplify Authentication to implement React Navigation Authentication Flow but I can't seem to figure out what's wrong. Whenever I click on the login button, this error appears.
Navigation Code (excluding imports):
const NavigationGeneral = () => {
const [user, setUser] = useState(undefined);
const checkUser = async () => {
try {
const authUser = await Auth.currentAuthenticatedUser({bypassCache: true});
setUser(authUser);
} catch (e) {
setUser(null);
}
}
useEffect(() => {
checkUser();
}, []);
if (user === undefined) {
return (
<View style = {{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator />
</View>
)
}
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
{user ? (
<>
<Stack.Screen name = "Login" component = {LoginScreen} />
<Stack.Screen name = "SignUp" component = {SignUpScreen} />
<Stack.Screen name = "ConfirmEmail" component = {ConfirmEmailScreen} />
<Stack.Screen name = "ForgotPassword" component = {ForgotPasswordScreen} />
<Stack.Screen name = "NewPassword" component = {NewPasswordScreen} />
</>
): (
<Stack.Screen name = "HomeScreen" component = {HomeTabNavigator} />
)}
</Stack.Navigator>
);
};
//* TAB NAVIGATOR FOR APP SCREENS
const HomeTabNavigator = () => {
return (
<Tab.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: false,
tabBarStyle: {backgroundColor: '#0052cc'},
tabBarInactiveTintColor: '#fff',
tabBarActiveTintColor: '#fff',
tabBarActiveBackgroundColor: '#006600'
}}>
<Tab.Screen
name="Home"
component={HomeScreen}
options={{
tabBarLabel: 'Home',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home-outline" color={'#fff'} size={25} />
),
}}
/>
<Tab.Screen
name="Courses"
component={CourseScreen}
options={{
tabBarLabel: 'Courses',
tabBarIcon: ({ color, size }) => (
<Ionicons name="library-outline" color={'#fff'} size={25} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: 'My Profile',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person-outline" color={'#fff'} size={25} />
),
}}
/>
<Tab.Screen
name="Forensic Tools"
component={ForensicToolsScreen}
options={{
tabBarLabel: 'Tools List',
tabBarIcon: ({ color, size }) => (
<Ionicons name="list-outline" color={'#fff'} size={25} />
),
}}
/>
<Tab.Screen
name="Leaderboard"
component={LeaderboardScreen}
options={{
tabBarLabel: 'Leaderboard',
tabBarIcon: ({ color, size }) => (
<Ionicons name="podium-outline" color={'#fff'} size={25} />
),
}}
/>
<Tab.Screen
name="Settings"
component={SettingsScreen}
options={{
tabBarLabel: 'Settings',
tabBarIcon: ({ color, size }) => (
<Ionicons name="settings-outline" color={'#fff'} size={25} />
),
}}
/>
</Tab.Navigator>
);
};
export default NavigationGeneral;
LoginScreen.js code snippet:
const onLoginPressed = async data => {
if (loading) {
return;
}
setLoading(true);
try {
await Auth.signIn(data.username, data.password);
navigation.navigate("HomeScreen");
console.log("Login");
} catch (e) {
Alert.alert('Oops', e.message);
}
setLoading(false);
};
I understand that I'm not supposed to manually navigate using navigation.navigate() but rather to conditionally define the screens. But whenever I remove navigation.navigate("HomeScreen"); from the code, nothing happens when I press the login button. So I assume something is wrong with my conditioning in my NavigationGeneral code, I just can't seem to figure out the problem.
Some help or additional tips would be greatly appreciated, thanks in advance. Please let me know if more info is required.
When you try to navigate to the HomeScreen I assume the user is not yet set in the state and therefore the screen with the name HomeScreen does not exist yet, so the navigator has nowhere to go.
Try setting the user in the NavigationGeneral upon login, it should event redirect automatically without using navigation.navigate.
You should not trigger navigation manually when implementing authentication flow with react-navigation. After the successful login, user will be truthy value. This means conditional rendering inside of navigation will handle it automatically.
Moreover, in case of condition user===undefined do not render LoadingIndicator, instead, create a new loading state and render LoadingIndicator when the state loading becomes true.
And I believe it should be !user instead of user, since not authenticated user (user===undefined) will want to see Login, Signup etc. screens.
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
// Here it should be !user
{!user ? (
<>
<Stack.Screen name = "Login" component = {LoginScreen} />
<Stack.Screen name = "SignUp" component = {SignUpScreen} />
<Stack.Screen name = "ConfirmEmail" component = {ConfirmEmailScreen} />
<Stack.Screen name = "ForgotPassword" component = {ForgotPasswordScreen} />
<Stack.Screen name = "NewPassword" component = {NewPasswordScreen} />
</>
): (
<Stack.Screen name = "HomeScreen" component = {HomeTabNavigator} />
)}
</Stack.Navigator>
);
const [checkAuthUserLoading, setCheckAuthUserLoading] = React.useState(false);
const user = useSelector((state) => state.user); // Retrieve user from redux store. (It can be mobx, zustand or any other state management.)
const checkUser = async () => {
try {
setCheckAuthUserLoading(true);
const authUser = await Auth.currentAuthenticatedUser({
bypassCache: true,
});
setCheckAuthUserLoading(false);
dispatch(setAuthUser(user)); // I would suggest to use state management such as redux or mobx instead of storing user in component state
} catch (e) {
dispatch(setAuthUserError(e)); // Here you do not need to setUser(undefined) since its already failed and will be stay as undefined
}
};
if (checkAuthUserLoading) {
return <LoadingIndicator />;
}

Can't pass parameters through React Navigation TabBar

Can't send any parameters with react-navigation 5x.
Tried everything on documentation. Don't know what's wrong.
I will share you all my route structure.
My ApplicationNavigator.js: I'm using drawer content with tabbar navigator at the same time. OrderNavigator is a tabnavigator, Dashboard is stacknavigator.
const ApplicationNavigator = () => {
const [isApplicationLoaded, setIsApplicationLoaded] = useState(false)
const applicationIsLoading = useSelector((state) => state.startup.loading)
return (
<Drawer.Navigator
drawerStyle={{ width: '100%' }}
drawerContent={(props) => <DrawerContent {...props} />}
>
<Drawer.Screen name="Dashboard" component={DashboardNavigator} />
<Drawer.Screen name="Order" component={OrderNavigator} />
</Drawer.Navigator>
)
}
export default ApplicationNavigator
My OrderNavigator.js: This is my tab.screen structure.
const ScreenA = () => {
return (
.....
<Stack.Navigator>
<Stack.Screen
options={headerStyle_1}
name="Orders"
component={IndexOrderContainer}
/>
</Stack.Navigator>
)
}
const ScreenB = () => {
....
return (
<Stack.Navigator>
<Stack.Screen
options={headerStyle_1}
name="New Order"
component={AddOrderContainer}
/>
</Stack.Navigator>
)
}
const OrderNavigator = () => {
return (
<Tab.Navigator headerMode={'none'}>
<Tab.Screen name="OrderList" component={ScreenA} />
<Tab.Screen name="NewOrder" component={ScreenB} />
</Tab.Navigator>
)
}
export default OrderNavigator
I'm trying to make redirect with code below. I'm using it with my order listing page, and trying to redirect to "order detail" page onclick. (with tab navigator.)
<TouchableOpacity
onPress={() =>
navigation.navigate('NewOrder', {
params: { user: 'jane' },
})
}
style={[styles.ListItem]}
button
key={`order${index}`}
>
And my response, when try to navigate with parameters is.
But you can see that my params is "undefined" with response.
const AddOrderContainer = ({ route, navigation }) => {
useEffect(() => {
console.log(route)
}, [route])
....
....
Object {
"key": "New Order-mafygLVPzFO1rVOhj1zVH",
"name": "New Order",
"params": undefined,
}

React navigation 5 stack navigator and drawer navigator together

Basically I am from react-navigation v4, I am familiar handling the same with SwitchNavigator as bridge, but v5 does no support for SwitchNavigator, so bit struggling to understand the below implementation.
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
function AuthenticationStack({ navigation }) {
return (
<Stack.Navigator initialRouteName="Home" screenOptions={{headerShown: false}}>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Registration" component={Registration} />
</Stack.Navigator>
);
}
function UserStack({ navigation }) {
return (
<Stack.Navigator initialRouteName="Jobs" screenOptions={{headerShown: false}}>
<Stack.Screen name="Jobs" component={Profile} />
<Stack.Screen name="JobDetails" component={JobDetails} />
</Stack.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Drawer.Navigator drawerContent={props => <SideBar { ...props } />}>
<Drawer.Screen name="Home" component={AuthenticationStack} />
<Drawer.Screen name="Jobs" component={UserStack} />
</Drawer.Navigator>
</NavigationContainer>
);
}
export default App;
The above code works but have some problems like,
While loading the app, initially the drawer is visible and goes hidden when the app loaded.
I do not want to have drawers for Authentication screens, If I have 2 different navigation without splitting AuthenticationStack and UserStack then I am facing problem while navigating from Login to Profile and vice versa
UPDATE 1
export default() => {
const [logged, setUser] = React.useState(false);
return (
<NavigationContainer>
{
logged
?
<DrawerScreen initialParams={{ setUser }} />
:
<AuthStackScreen initialParams={{ setUser }} />
}
</NavigationContainer>
);
}
Now in login.js, I need to update setUser to true, right? If yes how can I access setUser in my login.js file
Update 2
class Login extends Component {
fetch(login_url, {
method: "POST",
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Accept-Language': 'en-US' },
body: JSON.stringify(data)
})
.then((response) => {
const success = response.ok;
const data = response.json();
return Promise.all([success, data]);
})
.then(([success, response]) => {
var data, userInfo;
if (success) {
data = {
token: response.token,
user: response.user,
}
if(_storeUserData(response)) {
_retrieveUserData().then((userInfo) => {
this.setState({
logged: true,
userInfo: userInfo,
loading:false,
email: '',
password: ''
});
navigate('Profile');
})
.catch((error) => {
// alert(error);
});
}
}
this.setState(state_data);
})
.catch((error) => {
alert('Error:'+error);
this.setState({ loading: false});
});
}
You can create something similar to the switch navigator by conditionally rendering navigators like below.
export default function App() {
const [loggedIn, setLoggedIn] = React.useState(false);
return (
<NavigationContainer>
{loggedIn ? (
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} initialParams={{ setLoggedIn }}/>
<Drawer.Screen name="Notifications" component={NotificationsScreen} />
</Drawer.Navigator>
) : (
<Stack.Navigator>
<Stack.Screen
name="Auth"
component={AuthScreen}
initialParams={{ setLoggedIn }}
/>
</Stack.Navigator>
)}
</NavigationContainer>
);
}
You can tryout this snack for a working example
https://snack.expo.io/#guruparan/switchsample