How to implement auth flow in react native with react navigation? - react-native

Hi guys need help over Login flow, I write a code for auth flow but facing some weird issue like.
Right now what is happening I have login page while clicking on signIn button, I am sending the user to auth screen but if user click on back button then he moves to login screen again, and this is wrong, if user is login then why it's again move to login screen.
Here is code for it this is my route.js
import React from 'react';
import { StackNavigator, DrawerNavigator } from 'react-navigation'
import loginScreen from '../containers/login/loginScreen'
import todo from '../containers/todo/todo'
import addTodo from '../components/addTodoTask'
import SideBar from '../components/sideBar/sideBar'
import DashBoard from '../components/common/dashboardWidget'
import signUp from '../containers/signUp'
import editTodo from '../containers/todo/editTodo'
const authScreen = DrawerNavigator({
DashBoard: { screen : DashBoard },
Todo: { screen: todo }
},{
drawerPosition: 'left',
contentComponent: props => <SideBar {...props} />
},);
export const SignedOut = StackNavigator({
LoginScreen : {
screen : loginScreen,
navigationOptions : {
mode: "card",
title: "Sign in",
headerMode: "Screen",
header : null
}
},
signUp : { screen : signUp }
},{
headerMode: 'none'
},);
export const SignedIn = StackNavigator({
rootnavigator : { screen : authScreen },
addTodo : { screen : addTodo },
editTodo: { screen : editTodo }
},{
headerMode: 'none'
},);
export const createRootNavigator = (signedIn = false) => {
return StackNavigator(
{
SignedIn: {
screen: SignedIn,
navigationOptions: {
gesturesEnabled: false
}
},
SignedOut: {
screen: SignedOut,
navigationOptions: {
gesturesEnabled: false
}
}
},
{
headerMode: "none",
mode: "modal",
initialRouteName: signedIn ? "SignedIn" : "SignedOut"
}
);
};
and here is login button code
signIn() {
axios.post(BACKEND_URL+'/api/User/login', {
userName: this.state.userName,
password: this.state.password
})
.then( (response) => {
if(response.data.message == 'Success'){
// Set up root Auth Token and Headers
axios.defaults.headers.common['Authorization'] = "bearer "+response.data.data.accessToken;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
AsyncStorage.setItem('#userData:', JSON.stringify(response.data.data));
onSignIn().then(() => { this.props.navigation.navigate('SignedIn') });
this.setState({ isLoading: false });
}
})
.catch( (error) => {
alert("Please check your crendentials")
});
}

Instead of navigation.navigate try to navigation.dispatch to reset the routes to SignedIn StackNavigator. As,
Import NavigationActions from react-navigation. and use below code to dispatch your new route after user signedin.
onSignIn().then(() => { this.props.navigation.dispatch(
NavigationActions.reset({
index: 0,
key: null,
actions: [
NavigationActions.navigate({ routeName: 'SignedIn' })
]
})
)});
For more info read this Reset Navigation Action.

Related

How to use hook with SwitchNavigator

I'm trying to use https://github.com/expo/react-native-action-sheet with switch navigator.
I'm not sure how to do the basic setup as the example in the readme is different than my App.js. I'm using react 16.8 so I should be able to use hooks.
My App.js
import { useActionSheet } from '#expo/react-native-action-sheet'
const AuthStack = createStackNavigator(
{ Signup: SignupScreen, Login: LoginScreen }
);
const navigator = createBottomTabNavigator(
{
Feed: {
screen: FeedScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
Profile: {
screen: ProfileScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
},
);
const stackNavigator = createStackNavigator(
{
Main: {
screen: navigator,
// Set the title for our app when the tab bar screen is present
navigationOptions: { title: 'Test' },
},
// This screen will not have a tab bar
NewPost: NewPostScreen,
},
{
cardStyle: { backgroundColor: 'white' },
},
);
export default createAppContainer(
createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: stackNavigator,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
);
const { showActionSheetWithOptions } = useActionSheet();
);
Update, I'm getting this error when calling the showActionSheetWithOptions inside my component:
Hooks can only be called inside the body of a function component. invalid hook call
This is my code:
import React, { Component } from 'react';
import { useActionSheet } from '#expo/react-native-action-sheet'
export default class NewPostScreen extends Component {
_onOpenActionSheet = () => {
const options = ['Delete', 'Save', 'Cancel'];
const destructiveButtonIndex = 0;
const cancelButtonIndex = 2;
const { showActionSheetWithOptions } = useActionSheet();
showActionSheetWithOptions(
{
options,
cancelButtonIndex,
destructiveButtonIndex,
},
buttonIndex => {
console.log(buttonIndex);
},
);
};
render () {
return (
<View>
<Button title="Test" onPress={this._onOpenActionSheet} />
</View>
)
}
}
update 2
I also tried using a functional component, but the actionsheet does not open (console does print "pressed")
// ActionSheet.js
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import { useActionSheet } from '#expo/react-native-action-sheet'
export default function ActionSheet () {
const { showActionSheetWithOptions } = useActionSheet();
const _onOpenActionSheet = () => {
console.log("pressed");
const options = ['Delete', 'Save', 'Cancel'];
const destructiveButtonIndex = 0;
const cancelButtonIndex = 2;
showActionSheetWithOptions(
{
options,
cancelButtonIndex,
destructiveButtonIndex,
},
(buttonIndex) => {
console.log(buttonIndex);
},
);
};
return (
<TouchableOpacity onPress={_onOpenActionSheet} style={{height: 100,}}>
<Text>Click here</Text>
</TouchableOpacity>
);
};
Problem
As you can see here. You are not connecting your application root component.
Solution
import connectActionSheet from #expo/react-native-action-sheet and connect your application root component to the action sheet.
Simply modify your App.js to reflect the following:
// ... Other imports
import { connectActionSheet } from '#expo/react-native-action-sheet'
const AuthStack = createStackNavigator({
Signup: SignupScreen,
Login: LoginScreen
});
const navigator = createBottomTabNavigator({
Feed: {
screen: FeedScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
Profile: {
screen: ProfileScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
});
const stackNavigator = createStackNavigator({
Main: {
screen: navigator,
// Set the title for our app when the tab bar screen is present
navigationOptions: { title: 'Test' },
},
// This screen will not have a tab bar
NewPost: NewPostScreen,
}, {
cardStyle: { backgroundColor: 'white' },
});
const appContianer = createAppContainer(
createSwitchNavigator({
AuthLoading: AuthLoadingScreen,
App: stackNavigator,
Auth: AuthStack,
}, {
initialRouteName: 'AuthLoading',
})
);
const ConnectApp = connectActionSheet(appContianer);
export default ConnectApp;
Now on any of your application screens (i.e. Feed, Profile, Main, etc.) you can access the action sheet as follows:
If Stateless Component
// ... Other imports
import { useActionSheet } from '#expo/react-native-action-sheet'
export default function Profile () {
const { showActionSheetWithOptions } = useActionSheet();
/* ... */
}
If Statefull Component
// ... Other imports
import React from 'react'
import { useActionSheet } from '#expo/react-native-action-sheet'
export default Profile extends React.Component {
const { showActionSheetWithOptions } = useActionSheet();
/* ... */
}
Note: You can also access the action sheet as stated below from the docs
App component can access the actionSheet method as this.props.showActionSheetWithOptions

Component with fewer than 1 type argumanet in class delcaration

/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow
*/
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import { createBottomTabNavigator, createSwitchNavigator, createStackNavigator, createAppContainer } from 'react-navigation';
import Event from './src/components/event/Event.js';
import Chat from './src/components/chat/Chat.js';
import Signup from "./src/components/signup/Signup.js";
import Verif1 from "./src/components/signup/Verif1.js";
import NewEvent from "./src/components/event/Newevent.js";
import EditUser from "./src/components/user/Edituser";
import NewUser from "./src/components/user/Newuser";
import io from 'socket.io-client';
import GLOBAL from "./src/lib/global";
import SplashScreen from "./src/components/splashscreen/SplashScreen";
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
android:
'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
//socket.io
const socket = io(GLOBAL.BASE_URL, {
//const socket = io(GLOBAL.BASE_URL, {
transports: ['websocket'],
jsonp: false
});
console.log("socket id in App.js : ", socket.id);
socket.on('disconnect', (reason) => {
// ...
if (reason === 'io server disconnect') {
// the disconnection was initiated by the server, you need to reconnect manually
socket.connect();
}
// else the socket will automatically try to reconnect
});
const ChatWithSocket = (props) => (<Chat {...props} socket={socket} />)
const EventStack = {
Event: {
screen: Event,
navigationOptions: {
title: 'Event',
},
},
NewEvent: {
screen: NewEvent,
navigationOptions: {
title: 'New Event',
},
},
Chat: {
screen: ChatWithSocket,
navigationOptions: {
title: 'Chat',
},
},
};
const SignupStack = {
Signup: {
screen: Signup,
navigationOptions: {
title: 'Signup',
},
},
Verif1: {
screen: Verif1,
navigationOptions: {
title: 'Verify User',
},
},
};
const UserStack = {
NewUser: {
screen: NewUser,
navigationOptions: {
title: 'New User',
},
},
/* EditUser: {
screen: EditUser,
navigationOptions: {
title: 'Edit User',
},
}, */
};
const bottomTabNavOptions = {
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, tintColor }) => {
const { routeName } = navigation.state;
console.log("Navigation : ", navigation.dangerouslyGetParent().getParam('params'));
let iconName;
if (routeName === 'Event') {
iconName = `ios-information-circle${focused ? '' : '-outline'}`;
} else if (routeName === 'User') {
iconName = `ios-options${focused ? '' : '-outline'}`;
}
return <Text>Hello the world!</Text>
},
}),
tabBarOptions: {
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
},
};
class App extends React.Component { //<<<=== this line cause error
EventRoute = (initRoute) => {
createStackNavigator(EventStack, {
initialRouteName: initRoute,
})
};
UserRoute = (initRoute) => {
createStackNavigator(UserStack, {
initialRouteName: initRoute,
})
};
bottomTabScreen = () => {
//if there is a token and user
if (this.props.token && this.props.user) {
return createBottomTabNavigator(
{
Event: {screen: this.EventRoute("Event")},
User: {screen: this.UserRoute("User")},
}, bottomTabNavOptions
);
} else {
return createStackNavigator(
{
Signup: {screen: Signup},
Verif1: {screen: Verif1},
}
);
};
};
createDynamicRoutes = () => {
return createAppContainer(
createBottomTabNavigator(this.bottomTabScreen())
);
};
render() {
const AppContainer = this.createDynamicRoutes();
return <AppContainer />;
}
};
const InitialNavigator = createSwitchNavigator({
Splash: SplashScreen,
App: App,
});
export default createAppContainer(InitialNavigator);
But the declaration of class App extends React.Component {...}
shows the error in IDE VS Code:
Cannot use property `Component` [1] with fewer than 1 type argument.Flow(InferError)
react.js(26, 30): [1] property `Component`
Quick Fix...
Peek Problem
Based on my reading of latest document for react native 0.59, class App extends React.Component {...} is fine. Version of React navigation is 3.9.
Looks like flow validation error, add empty props to your component.\
type Props = {};
export default class App extends Component<Props> {
...
}

React Native - How to update screen on an already mounted component

I have 2 screens:
Home Screen
Login Screen
My entry point is my Home screen, if I am logged in (fetching datas), log out and then log in to another account. Since my Home component is already mounted, my data will not be updated and this.getDatas() will not be called again. I can't find solutions, I think I need to use "ComponentWillUpdate" in my Home.js to call this.getDatas() or something like that but you can't find the solution.
Here's my code:
Home.js
componentDidMount = async () => {
const { navigation } = this.props;
if (!await AsyncStorage.getItem('auth')) {
navigation.navigate('Login');
} else {
this.getDatas();
}
}
getDatas() {
axios.get(`${apiUrl}/me`).then((res) => {
this.setState({
datas: res.data.datas,
});
}).catch((err) => {
console.warn(err);
});
}
Login.js
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: null,
password: null,
};
}
logIn() {
const { email, password } = this.state;
axios.post(`${apiUrl}/auth`, {
email,
password
}).then((res) => {
navigation.navigate('Home');
}).catch((err) => {
console.warn('Error:', err);
});
}
}
App.js
const MainNavigator = createStackNavigator({
Home,
CreateGame,
GamePreview,
CardSelection,
BonusCard,
Game,
GameResult
}, { initialRouteName: 'Home', headerMode: 'none', });
const LoginNavigator = createStackNavigator({
Login,
Subscribe,
SubscribeNext,
ForgottenPassword,
}, { initialRouteName: 'Login', headerMode: 'none', });
const RootNavigator = createSwitchNavigator({
AuthLoading: AuthLoadingScreen,
LoginNavigator,
MainNavigator
}, { initialRouteName: 'AuthLoading' });
export default class App extends Component {
render() {
return <RootNavigator />;
}
}
Any idea ?
You should consider using Switch Navigator from react-navigation for your authentication flows in your application. So once you get logged out of your app and lands on login, the application screen pops out of the memory and thus when you login back, the componentDidmount will again be triggered.

Custom header in nested TabNavigator

I have a fairly complication navigation flow requirement for an app I'm working on.
I have a bottom tab bar, for each tab I'll be having a top tab bar for additional related views.
Which I have working, however on the videos tab in the nested "All" tab, I need to add a search bar to the header, and on the "Favourites" tab I'll be having yet another custom header with an "Edit" button at the top right.
How can I achieve this navigation whilst allowing React Navigation to co-ordinate everything. See images below:
What I don't want to do is disable the header at the MainNavigator level and enable it for particular routes. Or even worse embed the header and the tab bar on individual containers.
routes.js
import {
StackNavigator,
TabNavigator,
DrawerNavigator
} from "react-navigation";
import Feed from "./containers/Feed";
import Auth from "./containers/Auth";
import News from "./containers/News";
import Videos from "./containers/Videos";
import FavouriteVideos from "./containers/FavouriteVideos";
const DashboardNavigator = TabNavigator(
{
Feed: {
screen: Feed
},
News: {
screen: News
}
},
{
tabBarPosition: "top"
}
);
const VideoNavigator = TabNavigator(
{
Videos: {
screen: Videos,
navigationOptions: {
title: "All"
}
},
Favourites: {
screen: FavouriteVideos
}
},
{
tabBarPosition: "top"
}
);
const MainNavigator = TabNavigator(
{
Dashboard: {
screen: DashboardNavigator,
navigationOptions: ({}) => ({
title: "Dashboard"
})
},
Video: {
screen: VideoNavigator,
navigationOptions: ({}) => ({
title: "Videos"
})
}
},
{
swipeEnabled: false,
animationEnabled: false,
tabBarPosition: "bottom"
}
);
const AuthenticatedNavigator = DrawerNavigator({
App: {
screen: MainNavigator
}
});
const RootNavigator = StackNavigator({
LoggedOut: {
screen: Auth
},
Authenticated: {
screen: AuthenticatedNavigator
}
});
export default RootNavigator;
Snack
https://snack.expo.io/H1qeJrLiM
Images
You can use react-navigation addListener function with combination setParams to achieve desired behavior.
You can listen for focus and blur events and then change a parameter. Then in your route config you can look for this parameter and decide what to render for header. I changed your snack to show a working example of what I am suggesting.
Example
const MainNavigator = TabNavigator(
{
Dashboard: {
screen: DashboardNavigator,
navigationOptions: ({}) => ({
title: "Dashboard"
})
},
Video: {
screen: VideoNavigator,
navigationOptions: ({navigation}) => {
let title = 'Videos';
navigation.state.routes.forEach((route) => {
if(route.routeName === 'Videos' && route.params) {
title = route.params.title;
}
});
// I set title here but you can set a custom Header component
return {
tabBarLabel: 'Video',
title
}
}
}
},
{
swipeEnabled: false,
animationEnabled: false,
tabBarPosition: "bottom"
}
);
export default class Videos extends Component {
constructor(props) {
super(props);
this.willFocusSubscription = props.navigation.addListener(
'willFocus',
payload => {
this.props.navigation.setParams({title: 'All Videos'});
}
);
this.willBlurSubscription = props.navigation.addListener(
'willBlur',
payload => {
this.props.navigation.setParams({title: 'Just Videos'});
}
);
}
componentWillUnmount() {
this.willFocusSubscription.remove();
this.willBlurSubscription.remove();
}
render() {
return (
<View>
<Text> Videos </Text>
</View>
);
}
}

Remove screen from stack navigator

I’ve observed Back button logic works seeing the sequence of screens in the stack. I’ve a Drawer navigator placed inside stack navigator.
On top of the stack I’ve Splash screen. On Dashboard when I press back button it takes me to splash screen.
How can I remove splash screen from stack after entering in the app, So when I press back button Dashboard it will EXIT the app instead of taking to SPLASH SCREEN.
/* #flow */
import React from "react";
import { Platform, Text } from "react-native";
import { Root } from "native-base";
import { StackNavigator, DrawerNavigator} from "react-navigation";
import Register from "./components/Register";
import Available from "./components/Available";
import Splash from "./components/splash/“;
import SideBar from "./components/sidebar";
import Discover from "./components/Discover/";
import Dashboard from "./components/Dashboard/";
import Contact from "./components/Contact"
const Drawer = DrawerNavigator(
{
Dashboard: { screen: Dashboard },
Discover: { screen: Discover },
Contact: { screen: Contact},
},
{
navigationOptions: {
gesturesEnabled: false,
},
initialRouteName: "Dashboard",
contentOptions: {
activeTintColor: "#e91e63"
},
drawerPosition: 'right',
contentComponent: props => <SideBar {...props} />
}
);
const AppNavigator = StackNavigator(
{
Splash: { screen: Splash },
Drawer: { screen: Drawer },
Available: { screen: Available },
Register: { screen: Register },
},
{
// initialRouteName: “Splash”,
headerMode: "none",
}
);
export default () =>
<Root>
<AppNavigator />
</Root>;
One solution would be to reset the stack inside the splash screen component and redirect the user to the screen that you prefer:
import { NavigationActions } from 'react-navigation'
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Drawer'})
]
})
this.props.navigation.dispatch(resetAction)
For newer versions of react-navigation :
import { StackActions, NavigationActions } from 'react-navigation';
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Profile' })],
});
this.props.navigation.dispatch(resetAction);
Link to the official documentation
One of the simplest solutions would be, replace current screen with new screen so user won't be allowed to go back.
const SplashScreen: () => {
...
navigation.replace("LoginScreen"); // Move forward and Remove this screen from stack
see https://reactnavigation.org/docs/stack-actions/#replace
I solved it using this
code:
const prevGetStateForActionHomeStack = HomeStack.router.getStateForAction;
HomeStack.router = {
...HomeStack.router,
getStateForAction(action, state) {
if (state && action.type === 'ReplaceCurrentScreen') {
const routes = state.routes.slice(0, state.routes.length - 1);
routes.push(action);
return {
...state,
routes,
index: routes.length - 1,
};
}
return prevGetStateForActionHomeStack(action, state);
},
};
replaceScreen = () => {
const { locations, position } = this.props.navigation.state.params;
this.props.navigation.dispatch({
key: 'NearMeMap',
type: 'ReplaceCurrentScreen',
routeName: 'NearMeMap',
params: { locations, position },
});
};
Also if you need in-depth explanation of the code, watch this short video here
In react-navigation 5.x version. If you want to open new screen and remove previous stack screen, you can write: -
navigation.dispatch(StackActions.replace('screen_name', {params: {}}));
The replace action allows to replace a route in the navigation state. You can import it as:-
import { StackActions } from '#react-navigation/native';
If you want to perform other actions on stack navigation check out this link-
https://reactnavigation.org/docs/stack-actions
useEffect(() => {
setTimeout(() => {
navigation.reset({
index:0,
routes:[
{
name:"Welcome",
// params:{key:'param'},
},
],
});
}, 1000);},[]);
You can also trigger the this onPress like:
onPress={()=>navigation.reset({
index:0,
routes:[
{
name:"NameOfScreen",
// params:{key:'param'},
},
],
})}