React native how to add bottom tab menu after clicking login - javascript

I am new to react-native and I am confused about routing / navigation. Basically, I've got 4 screens which are Login, Register, Home and Links. The Register and Login are already set. I used stackNavigator in order for the user to click back whether they want to register or not. But when I click login, I want to redirect the user to my Home screen which has a tab menu or container. My current output for this code is that when I do register, I can go back to the login by pressing the text I made. Can someone show me or provide me some link on how to do the bottom tab when I successfully logged in?
My current output is here
https://imgur.com/a/9lsHCe6
I utilized onPress={()=>{navigation.navigate('Route')}} functionality from my components to switch screen.
Here is some of my code:
AppNavigation.js
import { createStackNavigator } from 'react-navigation-stack'
import Register from '../screens/RegisterScreen'
import Login from '../screens/LoginScreen'
const AppNavigation = createStackNavigator(
{
Login: { screen: Login},
Register: { screen: Register },
},
{
initialRouteName: 'Login',
headerMode: 'none'
}
)
export default AppNavigation
AuthNavigation.js
import { createStackNavigator } from 'react-navigation-stack'
import Login from '../screens/LoginScreen'
const AuthNavigation = createStackNavigator(
{
Login: { screen: Login },
},
{
initialRouteName: 'Login',
}
)
export default AuthNavigation
index.js (inside from ./navigation/index.js)
import { createSwitchNavigator, createAppContainer } from 'react-navigation'
import AuthNavigation from './AppNavigation'
import AppNavigation from './AppNavigation'
const SwitchNavigator = createSwitchNavigator(
{
Auth: AuthNavigation,
App: AppNavigation
},
{
initialRouteName: 'Auth',
}
)
const AppContainer = createAppContainer(SwitchNavigator)
export default AppContainer
App.js
export default function App() {
return (
<AppContainer />
);
}
}
My current directory
I followed the auth pattern here
https://heartbeat.fritz.ai/how-authentication-flow-works-in-react-native-apps-using-react-navigation-4-x-a30bb4d9e5d6

try this syntax on onPress
onPress={()=> this.props.navigation.navigate('Register')}
your ques: how to do the bottom tab when I successfully logged in?
create Home Component
When your login is successfully then
if(login is successfully){
this.props.navigation.navigate('Home');
}

Use the following syntax to be able to navigate between the components you want.
if(login == true){
this.props.navigation.navigate('Home');
// You can use this.props.navigation.push('Home'); as well
}
Here is a good reference to navigation and routing in general using React Native.

The way you can do this in React Navigation v5 and v6 are as followed. Setup a global auth provider with createContext. Then use that in a switch statement to determine whether you should use the Auth stack or the main navigation stack. The main navigation stack (AppStack in this case) would be a bottom tab navigator nested inside a stack navigator with the initialRouteName set to 'Home' (or whatever your home screen is called in the stack nav).
The Provider can look something like this
export const AuthUserProvider = ({ children }) => {
const [user, setUser] = useState(null);
return (
<AuthUserContext.Provider value={{ user, setUser }}>
{children}
</AuthUserContext.Provider>
);
};
And the Nested stack navigators with switch statement should look something like this:
export default function Routes() {
const Root = createStackNavigator();
const { user, setUser } = useContext(AuthUserContext);
//....authentication hooks
return (
<NavigationContainer>
<Root.Navigator
{user ?
(<Root.Screen name='AppStack' component={AppStack} />)
:
(<Root.Screen name='AuthStack' component={AuthStack} />)
}
<Root.Navigator>
<NavigationContainer>
)

Related

Navigate into nested navigator when Expo Push Noti is clicked

I am trying to navigate to a certain screen whenever I click on an Expo Push Notification. The screen that I want to navigate to is rather deep into the NavigationContainer.
However, the issue that I am facing now is being unable to even navigate to anywhere except having the app restart on its own. I'm running all the testing on a real device.
I'm using Expo to work on this school project.
I have only managed to find this question in SO and Expo Forums (duplicate) useful.
This is my application Navigation structure:
-Navigation Structure-
AppNavigator
DrawerNavigator
MainNavigator
TabsNavigator
StackNavigator
StackNavigator
TabsNavigator
ScreenA (Want to navigate to)
ScreenB (Want to navigate to)
StackNavigator
ScreenA
ScreenB
StackNavigator
ScreenA
AuthNavigator
RegisterNavigator
ScreenA
There is a useNotifications hook created and I called it in the main App Navigator where the NavigationContainer resides in.
import React, { useEffect } from 'react';
import * as Notifications from 'expo-notifications';
import navigation from '../navigation/RootNavigation';
const useNotifications = () => {
const notiResponseListener = React.createRef();
useEffect(() => {
notiResponseListener.current =
Notifications.addNotificationResponseReceivedListener(res => {
console.log(res.notification.request.content.data);
console.log('addNotificationResponseReceivedListener');
navigation.navigate(
('DrawerNavigator', { screen: 'ChangePassword' }),
{}
);
});
return () =>
Notifications.removeNotificationSubscription(notiResponseListener);
}, []);
};
export default useNotifications;
There is a ref added to the NavigationContainer.
import { navigationRef } from '../navigation/RootNavigation';
import useNotifications from '../hooks/useNotifications';
const App = createStackNavigator();
const AppNavigator = () => {
useNotifications();
return (
<NavigationContainer ref={navigationRef}>
<App.Navigator headerMode='none'>
...
</App.Navigator>
</NavigationContainer>
);
};
And lastly, the file that contains the ref used in the NavigationContainer.
import React from 'react';
export const navigationRef = React.createRef();
const navigate = (name, params) => {
console.log('entered navigating'); // does not print
navigationRef.current?.navigate(name, params);
};
export default {
navigate
};
I have searced high and low but I can't seem to find out what's wrong. Looked at the documentation for Expo and React Navigation but I'm not sure what's going on. It's my first time working on Push Notifications and such a case.
I appreciate any help, thank you
We have fixed the problem with the usage of useLastNotificationResponse.
const [notification, setNotification] = useState(false);
const notificationListener = useRef();
const responseListener = useRef();
//add this
const lastNotificationResponse =
Notifications.useLastNotificationResponse();
useEffect(() => {
if (lastNotificationResponse) {
//console.log(lastNotificationResponse);
//get the route
const route = JSON.stringify(
lastNotificationResponse.notification.request.content.data.route
);
//use some function to return the correct screen by route
getFullPath(JSON.parse(route));
}
}, [lastNotificationResponse]);
Based on your routes, navigate to correct screen
getFullPath:
import { navigationRef } from "./rootNavigation";
import routes from "./routes";
export function getFullPath(route) {
switch (route) {
case "HomeScreen":
return navigationRef.current?.navigate(routes.HOME);
case "Account":
return navigationRef.current?.navigate(routes.ACCOUNT, {
screen: routes.ACCOUNTSCREEN,
});
default:
return;
}
}

TypeError: Cannot read property 'navigate' of undefined - React native

I'm using Firebase with react-native to authenticate users. My main file is App.js and I'm sending the users to Login component and I have two components to manage routes. Appnavigator.js for creating a switchNavigator and DrawerNavigator.js for createDrawernavigator. After authentication, I want to send the user to one inner component (Admin).
Following are the codes for particular pages,
Login.js
export default class Login extends React.Component {
state = {
email:"",
password: "",
errorMessage: null
}
handleLogin = () => {
const { email , password } = this.state;
firebase.auth()
.signInWithEmailAndPassword(email, password)
.then(() => this.props.navigation.navigate('Admin'))
.catch(error => alert(error))
}
render(){
.........................
<TouchableOpacity style={styles.loginBtn} onPress = {
this.handleLogin
}>
And I have two components to manage login activities.
DrawerNavigator.js
import Admin from '../screens/Admin.js';
import Login from '../screens/Login.js'
import menu from '../assets/drawer.png';
const DrawerNavigator = createDrawerNavigator({
Admin: Admin,
},
{
drawerOpenRoute: 'openDrawer',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle'
},
);
export default DrawerNavigator;
And I have AppNavigator page to create an AppContainer and switchnavigator.
import React from 'react';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import DrawerNavigator from './DrawerNavigator.js';
import Login from '../screens/Login.js';
import App from '../App.js';
export default createAppContainer(
createSwitchNavigator({
Main:DrawerNavigator,
})
);
I have used this.props.navigation.navigate('Admin') In the handleLogin function to route the user after a successful login. But I'm getting an error
TypeError: Cannot read property 'navigate' of undefined
App.js file
import React from 'react';
import { StyleSheet, Platform, Text, View, Image, TextInput, TouchableOpacity } from 'react-native';import AppNavigator from './navigation/AppNavigator.js';
import Login from './screens/Login.js';
import AppNavigator from '../navigation/AppNavigator.js;
export default class App extends React.Component {
render(){
return (
<Login />
<AppNavigator />
);
}}
You have not added your Login component as a part of AppNavigator:
export default createAppContainer(
createSwitchNavigator({
Main:DrawerNavigator,
Auth: createStackNavigator(
{
Login: Login
}
)
})
);

Using Redux for making protected routes with React-Navigation (v.3 or v.4)

How can we use redux to create protected routes in react-navigation?
Consider I have a redux store containing following state
const mapStateToProps = state => {
return {
isAuthenticated: state.profileInfo.isAuthenticated,
isLoaded: state.profileInfo.isLoaded,
googleProfileLoading: state.profileInfo.googleProfileLoading
}
};
And I am using React-navigation to navigate user.
const loginNavigation = createStackNavigator(
{
login: {
screen: Login
},
signup: {
screen: Signup
}
},
{
headerMode: "none"
}
)
const allRoutes = createSwitchNavigator(
{
home: {
screen: loginNavigation
},
user: {
screen: user
},
{
initialRouteName: "home"
}
);
const App = createAppContainer(allRoutes);
Now if user is not logged in, I want redirect to login screen.
In simple react-redux, this is what I usually used to do: https://github.com/irohitb/SelfieApp/blob/master/client/src/routes.js
Can someone help me in figuring out how can we create protected routes in react-native, redux and react-navigation
react-navigation has createSwitchNavigator exactly for cases like this Authentication Flow.
You have to group your protected routes in one MainStack. Use createStackNavigator:
const MainStack = createStackNavigator(
{
home: { screen: HomeScreen },
events: { screen: EventsScreen },
profile: { screen: ProfileScreen },
...
{
initialRouteName: "home"
}
);
Then, config your authentication stack, again using createStackNavigator:
const AuthStack = createStackNavigator({
login: { screen: LoginScreen },
register: { screen: RegisterScreen },
forgot: { screen: ForgottenPasswordScreen },
...
});
And now is coming the createSwitchNavigator - to load the MainStack stack or the AuthStack:
const Routes = createSwitchNavigator({
initialLoading: InitialLoadingScreen,
auth: AuthStack,
all: MainStack,
}, {
initialRouteName: 'initialLoading',
});
export default createAppContainer(Routes);
The createSwitchNavigator will load InitialLoadingScreen that holds the logic if the user is authenticated or not:
class InitialLoadingScreen extends React.Component {
constructor(props) {
super(props);
this.bootstrapAsync();
}
bootstrapAsync = async () => {
// Load the home screen for the logged in users
if (this.props.isAuthenticated) {
return this.props.navigation.navigate('home');
}
// load the Auth screen if the user is NOT logged in
this.props.navigation.navigate('login');
}
// Render any loading content that you like here
render() {
return (
<View style={styles.container}>
<ActivityIndicator />
</View>
);
}
}
const mapStateToProps = ({ settings }) => ({
isAuthenticated: state.profileInfo.isAuthenticated,
isLoaded: state.profileInfo.isLoaded,
googleProfileLoading: state.profileInfo.googleProfileLoading
});
export default connect(mapStateToProps)(InitialLoadingScreen);
As you can see, you can connect InitialLoadingScreen to the redux store, to access any data and use it for your routing logic :)
So you have one redux state variable isAuthenticated.
In every screen you'll need to check for this state, if not logged in then thow user to login screen and reset navigation stack.
you can check in any life cycle method which will be called on first run,
for e.g., constructor, componentWillMount, componentDidMount, etc...
for redux state checking,
if(!this.props.isAuthenticated){
this.props.logoutAction();
this.props.navigation.navigate("Login");//Login or whatever your first screen is...
}
and for clearing whole stack,you'll need to create action which we called in above section.
export const logoutAction = () => {
return dispatch => dispatch({ type: LOGOUT_SUCCESS });
};
and you have to change some logic in file where you have written combine reducer's code,
const appReducer = combineReducers({
loginReducers: LoginReducers,
...
...
});
export default (rootReducer = (state, action) => {
if (action.type == LOGOUT_SUCCESS) {
state = undefined;
}
return appReducer(state, action);
});
this will reset whole reducer.
Now if you want to make logout button then onPress of button just change isAuthenticated to false and fire navigate method and logout action that's all.
you can use react-native router flux for navigation
https://www.npmjs.com/package/react-native-router-flux
sample:
<Text
style={alerts.btn}
onPress={
() => Actions.question(
// send data
{
'code': data_1,
})}>
Go to Home
</Text>

Adding an event handler on a createBottomTabNavigator of React Native

I have a React Native app which main navigation is accomplished using a
createBottomTabNavigator
as follows:
import React from 'react';
import { createBottomTabNavigator, createAppContainer } from 'react-navigation';
import Icon from 'react-native-vector-icons/Ionicons'
import HomeStack from './HomeStack'
import SettingsStack from './SettingsStack'
const TabNavigator = createBottomTabNavigator({
Home: {
screen: HomeStack,
navigationOptions: {
tabBarIcon:({tintColor})=>(
<Icon name="ios-home" color={tintColor} size={24}/>
)
}
},
Settings: {
screen: SettingsStack,
navigationOptions: {
tabBarIcon:({tintColor})=>(
<Icon name="ios-settings" color={tintColor} size={24}/>
)
}
}
});
export default createAppContainer(TabNavigator)
As you can see, it basically includes two other components that are stack navigators themselves. I will not include them in order to have cleaner question. What I've added to my app is real-time push notifications as described here. Everything seems to be working fine until now but I have added the notification handler:
_handleNotification = (notification) => {
this.setState({notification: notification});
};
in my HomeScreen which is part of my HomeStack (The first screen). I don't really like this structure (having the handler in my HomeScreen). I have another screen (in the same HomeStack) that I'd like to handle this new coming notification. But I assume it is not gonna happen until this other screen is not mounted. So, I was wondering, is it possible to somehow define the handler on the level of the main TabNavigator and when handled just to redirect to my dedicated screen? I assume this is a bit cleaner approach.
This is easy to implement if you just create a custom Tab Bar component.
TabBar config
import CustomTabBar from '???';
const TabRoutes = createBottomTabNavigator({
Home: {screen: HomeStack},
Settings: {screen: SettingsRoutes}
},{
tabBarComponent: CustomTabBar,
});
export default createAppContainer(TabNavigator)
CustomTabBar
import CustomTabBarItem from '???';
class CustomTabBar extends React.Component {
public constructor(props: Props) {
super(props);
this.state = {
notification: undefined
};
}
render() {
const {navigation} = this.props;
const routes = navigation.state.routes;
return (
<View>
{routes.map(route => {
// Just some basic example, create a CustomTabBarItem according your own needs
<CustomTabBarItem
key={route.key}
onPress={() => navigation.navigate(route.routeName)}
routeName={route.routeName}
/>
})}
</View>
);
}
_handleNotification = (notification) => {
this.setState({notification: notification});
};
}

Render two navigators

There is a Login.js module, with this module the user can log in. If the user data is correct, then the user switches to another Secured.js module. For switching from the module to the module I use StackNavigator. In the Secured.js module, Tabs should be displayed below. For this I use TabNavigator. The problem is that when I try to render tabs, it does not work for me, because React Native says that two navigators can not be created on one page. I tried to google, but I could not find anything worthwhile, except for the advice to update React Native, saying "this is a bug of the old version" (I tried too, but it didn't help). Is there any ideas how I can fix it?
Secured.js
import React, { Component } from 'react';
import {
ScrollView,
Text,
View,
Button,
StyleSheet,
FlatList
} from 'react-native';
// Import getNews function from news.js
import { getNews } from '../screens/news';
// We'll get to this one later
import Article from '../components/Article';
import Tabs from '../screens/Tabs';
export default class Secured extends React.Component {
static navigationOptions = {
title: "Neuigkeiten",
headerTintColor: "#FF0000",
headerLeft: null,
};
constructor(props) {
super(props);
this.state = { articles: [], refreshing: true };
this.fetchNews = this.fetchNews.bind(this);
}
// Called after a component is mounted
componentDidMount() {
this.fetchNews();
}
fetchNews() {
getNews()
.then(articles => this.setState({ articles, refreshing: false }))
.catch(() => this.setState({ refreshing: false }));
}
handleRefresh() {
this.setState(
{
refreshing: true
},
() => this.fetchNews()
);
}
render() {
return (
<View>
<FlatList
data={this.state.articles}
renderItem={({ item }) => <Article article={item} />}
keyExtractor={item => item.url}
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh.bind(this)}
/>
<View><Tabs /></View>
</View>
);
}
}
Tabs.js
import React, { Component } from 'react';
import { createBottomTabNavigator } from 'react-navigation';
import Secured from '../screens/Secured';
import Page1 from '../screens/Page1';
import Page2 from '../screens/Page2';
import Page3 from '../screens/Page3';
export default createBottomTabNavigator({
/// Secured: {screen:Secured,},
Page1: {screen:Page1,},
Page2: {screen:Page2,},
Page3: {screen:Page3,}
});
Firstly, it's not all happening because of react-native version is not updated. it's all about react-native navigation wrong use.
Let me explain you with two examples, one is wrong way similar with your case and second one is highly prefer, correct way to use navigators.
In React Native Navigation,
WRONG WAY
export default App extends React.Component {
render() {
/* In the root component we are rendering the app navigator */
return <AppNavigator />;
}
}
const AuthenticationNavigator = createStackNavigator({
SignIn: SignInScreen,
ForgotPassword: ForgotPasswordScreen,
});
class AuthenticationScreen extends React.Component {
render() {
return (
<AuthenticationNavigator />
);
}
}
const AppNavigator = createSwitchNavigator({
Auth: AuthenticationScreen, // This screen renders a navigator!
Home: HomeScreen,
});
In a screen inside of the navigator we are rendering another navigator You should avoid this! It will have its own navigation state and be unable To interact with any parent navigator, eg: it would not know the route "Home" exists
CORRECT & HIGHLY RECOMMENDED WAY
export default App extends React.Component {
render() {
return <AppNavigator />;
}
}
const AuthenticationNavigator = createStackNavigator({
SignIn: SignInScreen,
ForgotPassword: ForgotPasswordScreen,
});
const AppNavigator = createSwitchNavigator({
/*
* Rather than being rendered by a screen component, the
* AuthenticationNavigator is a screen component
*/
Auth: AuthenticationNavigator,
Home: HomeScreen,
});
IN YOUR CASE
import React, { Component } from 'react';
import { createBottomTabNavigator } from 'react-navigation';
import Secured from '../screens/Secured';
import Page1 from '../screens/Page1';
import Page2 from '../screens/Page2';
import Page3 from '../screens/Page3';
const Tabnav = createBottomTabNavigator({
/// Secured: {screen:Secured,},
Page1: {screen:Page1,},
Page2: {screen:Page2,},
Page3: {screen:Page3,}
});
and return the Tabnav in render function , instead of export the default. because we can't export more than one in one place.

Categories

Resources