React Navigation with Auth screen in every tab - reactjs

I have an app with this kind of structure
It doesn't matter if user is logged in or not, the TabBar always should be the same. In case user logged in app will open the content of the tab, otherwise Auth screen. How can I create this in react-navigation?
Sorry, slightly confused in react-navigation.

So I think I have a solution.
Create an event emitter that will emit events when the login state changes
Subscribe to the events in the App.js
Pass the updated events via screenProps to the TabNavigator.
These updated screenProps allow toggling between logged in states in each tab.
Store the logged in state in AsyncStorage so that it will be persisted between app closes. Note the AsyncStorage only allows you to store strings.
Send events from the AuthScreen when the user logs in, or from the other screens when the user logs out.
Event Emitter
To create an event emitter we need to use the events dependency. Install it with npm i events. We will create it as a singleton so that we can only have one instance of this running in our app.
import { EventEmitter } from 'events';
class LoginEventEmitter {
constructor () {
this.eventEmitter = new EventEmitter();
}
// create a function to handle the login
// pass the loggedIn value that you want to be emitted
handleLogin = (loggedIn) => {
this.eventEmitter.emit('loggedIn', { loggedIn });
}
}
const EventEmitterController = new LoginEventEmitter();
export default EventEmitterController;
App.js
Subscribe to the events and handle AsyncStorage
import React from 'react';
import { AsyncStorage } from 'react-native';
import AppContainer from './MainNavigation';
import LoginEventEmitter from './LoginEventEmitter';
export default class App extends React.Component {
constructor (props) {
super(props);
this.state = {
loggedIn: false,
loaded: false
};
// subscribe to the events and update the values in state and in AsyncStorage
LoginEventEmitter.eventEmitter.addListener('loggedIn', e => {
this.setState({ loggedIn: e.loggedIn });
let value = e.loggedIn ? 'true' : 'false';
AsyncStorage.setItem('loggedIn', value);
});
}
async componentDidMount () {
// handle the loggedIn value when the component mounts
try {
let loggedIn = await AsyncStorage.getItem('loggedIn');
if (loggedIn) {
this.setState({
loggedIn: loggedIn === 'true',
loaded: true
});
} else {
this.setState({ loaded: true });
}
} catch (error) {
console.warn(error);
}
}
render () {
// wait until asyncstorage has returned a value before showing the App.
// pass the loggedIn value via screen props so every screen in the TabNavigator gets updated with the new value
if (this.state.loaded) {
return (
<AppContainer screenProps={{ loggedIn: this.state.loggedIn }}/>
);
} else {
return null;
}
}
}
Template for Tab Screen
This is a basic template for each TabScreen. I have put a logout button on it so that the user can logout. (This is mainly for testing).
import React from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import AuthScreen from './AuthScreen';
import LoginEventEmitter from './LoginEventEmitter';
export default class Screen1 extends React.Component {
logout = () => {
LoginEventEmitter.handleLogin(false);
}
render () {
if (this.props.screenProps.loggedIn) {
return (
<View style={styles.container}>
<Text>Screen 1</Text>
<Button title={'logout'} onPress={this.logout} />
</View>
);
} else {
return <AuthScreen />;
}
}
}
Notice in the render function that we access the screenProps that are passed from the TabNavigator depending on the value that is passed depends on what is rendered.
A sample AuthScreen
Here we perform a "login" when the user taps the button. This emits an event that is caught by the listener in the App.js which in turn updates the state value in the App.js and that value is passed via screenProps to each Tab.
import React from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import LoginEventEmitter from './LoginEventEmitter';
export default class Screen1 extends React.Component {
render () {
return (
<View style={styles.container}>
<Text>AuthScreen</Text>
<Button title={'Login'} onPress={() => {
LoginEventEmitter.handleLogin(true);
}}/>
</View>
);
}
}
Sample TabNavigator
Here is a sample TabNavigator.
import Screen1 from './Screen1';
import Screen2 from './Screen2';
import { createBottomTabNavigator, createAppContainer } from 'react-navigation';
const screens = {
Screen1: {
screen: Screen1
},
Screen2: {
screen: Screen2
}
};
const config = {
headerMode: 'none',
initialRouteName: 'Screen1'
};
const MainNavigator = createBottomTabNavigator(screens, config);
export default createAppContainer(MainNavigator);
Snack
Finally here is a snack showing it all working https://snack.expo.io/#andypandy/event-emitter-to-handle-login

Related

How to add click action for push notification in React native

I want to navigate to particular app screen after click on remote push notification.
How can i implement this with react native. I am using react-native-push-notification library
react-native-push-notification has an on notification press handler -> onNotification which is called every time the user presses or has received a notification (this is from the react-native-push-notification documentation). In an application you create a NavigationService component which uses the top level navigator reference set in your App.js file. With this navigator, in the onNotification callback you can then navigate to any screen in your application and use the notification data you have received for your purposes, which can be passed to the navigating screen through the navigator (in its state). Some psuedo code can look like this:
NotificationService.js:
import NavigationService from './NavigationService';
import PushNotification from "react-native-push-notification";
PushNotification.configure({
onNotification: function(notification) {
const { data } = notification;
NavigationService.navigate('Screen', { notificationData: data });
}
});
NavigationService.js:
import { NavigationActions } from 'react-navigation';
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
Edit: Also you have to create an AppContainer, (This is placed in your App.js)
import { createSwitchNavigator } from 'react-navigation';
import { createAppContainer } from 'react-navigation';
const AppContainer = createAppContainer(createSwitchNavigator({...Screens}));
export default class App extends React.Component {
...
render() {
return (
<View>
<AppContainer ref={ (navigatorRef) => { NavigationService.setTopLevelNavigator(navigatorRef); } } />
</View>
)
}
}

How can I create React Native Navigation route?

I am setting up a new route, and want to support about making new routes. What can I do?
My AppNavigator.js - Routes are in here
import { createStackNavigator, createAppContainer } from "react-navigation";
import Login from "../client/components/auth/Login";
import Register from "../client/components/auth/Register";
const AppNavigator = createStackNavigator({
Login: { screen: Login },
Register: { screen: Register }
});
const App = createAppContainer(AppNavigator);
export default App;
In App.js, I just imported AppNavigator
Register.js - I am trying to route to Login
import React from "react";
import {
StyleSheet,
Text,
View,
TextInput,
Button,
TouchableOpacity
} from "react-native";
import PropTypes from "prop-types";
export default class Register extends React.Component {
render() {
return (
<View style={styles.container}>
<Text onPress={() => this.props.navigation.navigate("Login")}>
Have account? Login
</Text>
</View>
);
}
}
Create a new stack with createStackNavigator. As far as I understand you want to redirect to another route after Log in. For that, you have to add them createSwitchNavigator. Then after successful login, you will redirect your route for example settings and for successful login you save the token in Asyncstorage. So, every time you load the app if you are successfully logged in you will be redirected to Setting page automatically otherwise the registration page will be loaded.
Here the code for App.js
App.js
import { createStackNavigator, createAppContainer,createSwitchNavigator } from "react-navigation";
import Login from "../client/components/auth/Login";
import Register from "../client/components/auth/Register";
import Setting from "../client/components/auth/Setting";
class AuthLoadingScreen extends React.PureComponent {
constructor(props) {
super(props);
this._bootstrapAsync();
}
async _getStoredDataFromAsync(key) {
return await AsyncStorage.getItem(key);
}
_bootstrapAsync = async () => {
const userToken = await this._getStoredDataFromAsync("userToken");
this.props.navigation.navigate(userToken ? 'App' : 'Some');
};
render() {
return (
<View style={{flex:1}}>
<ActivityIndicator />
</View>
);
}
}
const AppNavigator = createStackNavigator({
Login: { screen: Login },
Register: { screen: Register }
});
const SomeNavigator = createStackNavigator({
Setting: { screen: Setting},
});
const Navigation = createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppNavigator ,
Some: SomeNavigator ,
},
{
initialRouteName: 'AuthLoading',
});
const App = createAppContainer(Navigation );
export default App;
you can use react-native router flux for navigation
https://www.npmjs.com/package/react-native-router-flux

return from navigation in react native give me undefined in this.props.navigation.state.params.filePath

i need help :).
my project is 2 pages in react native, MainPage and SoundRecord.
my init screen is MainPage and when i press the button 'take sound'
i move to another component to record sound(i move with react native navigation).
when i come back i want to return the filePath(where it save the file..).
i want to insert it to the state.
when i do this in MainPage:
this.state{
filePath: this.props.navigation.state.params.filePath
}
it give error:
undefined is not an object(evaluating 'this.props.navigation.state.params.filePath')
and i understand this because i start my project with MainPage and i dont have the filePath from the SoundRecord page.
what can i do?
can i do check if this.props.navigation.state.params.filePath !== undefined?
how to do it? i try almost everything...
i put the relevant code:
MainPage:
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import ImagePicker from 'react-native-image-picker';
import { RNS3 } from 'react-native-aws3';
import { aws } from './keys';
import SoundRecord from './SoundRecord'
export default class MainPage extends Component {
constructor(props){
super(props);
this.state={
file:'' ,
config:'',
filePath: '',
fileName: '',
tag:''
};
}
MoveToSoundRecordPage = () => {
this.props.navigation.navigate('SoundRecord');
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.MoveToSoundRecordPage}>
<Text>take sound</Text>
</TouchableOpacity>
{/* <SoundRecord takeSound={this.takeSound}/> */}
<Text>{this.state.fileName}</Text>
<TouchableOpacity onPress={this.UploadToAWS.bind(this)}>
<Text>Upload To Aws</Text>
</TouchableOpacity>
</View>
);
}
}
SoundRecord when i finish to record i send the filePath like this:
finishRecord = (filePath) => {
this.props.navigation.navigate('MainPage',{filePath});
}
thank!
If you want to update something on the previous page then you can do it in the following way.
Create a function in the MainPage that updates the state with the new filepath.
Pass the function as as a param.
Use the passed function in SoundRecord to update the state in the MainPage
MainPage
In your MainPage add the following
sendFilepath = (filePath) => {
this.setState({filePath})
}
Update the MoveToSoundRecordPage function to take the sendFilepath as a parameter:
MoveToSoundRecordPage = () => {
this.props.navigation.navigate('SoundRecord', { sendFilepath: this.sendFilepath });
}
SoundRecord
Then in the SoundRecord page you want to update the finishRecord so that it calls the function that you have passed.
finishRecord = (filePath) => {
this.props.navigation.state.params.sendFilepath(filePath)
this.props.navigation.goBack();
}
Snack
Here is a snack https://snack.expo.io/#andypandy/navigation-passing-function that shows passing a function called sendFilepath from Screen1 to Screen2. Then in Screen2 it then calls the function that was passed and this updates the state in Screen1.
Please try this. It may help you
Add this in MainPage screen:
componentWillMount() {
const filePath = this.props.navigation.getParam('filePath', '');
this.setState({ filePath:filePath })
}

Navigate to home screen if token is stored react-native o.w. login screen

It's been couple of days I am stuck at a point where as a beginner I am not sure how to take it ahead.
In my routes/main.js, I have this stack navigator:
export const createRootNavigator = (loggedIn = false) => {
return StackNavigator(
{
LoggedIn: {
screen: LoggedIn
},
LoggedOut: {
screen: LoggedOut
}
},
{
headerMode: loggedIn ? '' : 'none',
mode: Platform.OS === 'ios' ? 'modal' : 'card',
initialRouteName: loggedIn ? "LoggedIn" : "LoggedOut"
}
);
}
App.js
import RootNavigation from './root';
const App = () => ( {
render() {
return (
<Provider store = {store} >
<View style={styles.container}>
<RootNavigation/>
</View>
</Provider>
);
}
})
root.js
import React, {Component} from 'react';
import { AsyncStorage} from 'react-native';
import { createRootNavigator} from './src/routes/main';
let RootNavigation = createRootNavigator(true);
export default class Root extends Component {
render() {
return (
< RootNavigation / >
);
}
}
In root.js let RootNavigation = createRootNavigator(true); currently true is being passed, however it may be true or false which I supposed to get using AsyncStorage.getItem('authToken');
So I achieve something like this in my root.js
Note: Below code is incorrect but it explains what I want.
import React, {Component} from 'react';
import {AsyncStorage} from 'react-native';
import { createRootNavigator} from './src/routes/main';
// Not sure about the code placement of this part.
let RootNavigation;
let token = await AsyncStorage.getItem('authToken');
if(token) {
RootNavigation = createRootNavigator(true);
}else {
RootNavigation = createRootNavigator(false);
}
export default class Root extends Component {
render() {
return (
< RootNavigation / >
);
}
}
How can achieve this functionality?
I am using react-redux and react-thunk in my project.
Please help!!
Instead of rendering different RootNavigations based on whether the user is logged in or not. You may look at initializing your stack with a Splash Screen that checks if there is an authenticated user. Since you are using redux, a convenient place to store this information might be in your store.
Once your Splash Screen mounts and you've asynchronously identified if there is an active user or not, you can then push to the login screen or first authenticated screen.
Headers on screens that change based on whether a user if authenticated or not are best configured using the static navigationOptions method, rather than configuring it on the stack.
Hope that helps! Let me know if you have any questions.

How to get current navigation state

I am using React Navigation's Tab Navigator from https://reactnavigation.org/docs/navigators/tab, when I switch between the Tab Screens I don't get any navigation state in this.props.navigation.
Tab Navigator:
import React, { Component } from 'react';
import { View, Text, Image} from 'react-native';
import DashboardTabScreen from 'FinanceBakerZ/src/components/dashboard/DashboardTabScreen';
import { TabNavigator } from 'react-navigation';
render() {
console.log(this.props.navigation);
return (
<View>
<DashboardTabNavigator />
</View>
);
}
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
},
THISWEEK: {
screen: DashboardTabScreen
}
});
DASHBOARD SCREEN:
import React, { Component } from 'react';
import { View, Text} from 'react-native';
export default class DashboardTabScreen extends Component {
constructor(props) {
super(props);
this.state = {};
console.log('props', props);
}
render() {
console.log('props', this.props);
return (
<View style={{flex: 1}}>
<Text>Checking!</Text>
</View>
);
}
}
I get props at Dashboard Screen when it renders the component first but I don't get props when I switch the tabs.
I need to get the current Screen name i.e TODAY or THISWEEK.
Your problem is about "Screen Tracking", react-navigation has an officially guide for this. you can use onNavigationStateChange to track the screen by using built-in navigation container or write a Redux middleware to track the screen if you want to integrate with Redux. More detail can be found at the officially guide: Screen-Tracking. Below is a sample code from the guide by using onNavigationStateChange:
import { GoogleAnalyticsTracker } from 'react-native-google-analytics-bridge';
const tracker = new GoogleAnalyticsTracker(GA_TRACKING_ID);
// gets the current screen from navigation state
function getCurrentRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getCurrentRouteName(route);
}
return route.routeName;
}
const AppNavigator = StackNavigator(AppRouteConfigs);
export default () => (
<AppNavigator
onNavigationStateChange={(prevState, currentState) => {
const currentScreen = getCurrentRouteName(currentState);
const prevScreen = getCurrentRouteName(prevState);
if (prevScreen !== currentScreen) {
// the line below uses the Google Analytics tracker
// change the tracker here to use other Mobile analytics SDK.
tracker.trackScreenView(currentScreen);
}
}}
/>
);
Check all properties first, like
<Text>{JSON.stringify(this.props, null, 2)}</Text>
Above json array will show you current state of navigation under routeName index i.e.
this.props.navigation.state.routeName
Have you tried to define navigationOptions in your route object?
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
navigationOptions: {
title: 'TODAY',
},
},
})
You can also set navigationOptions to a callback that will be invoked with the navigation object.
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
navigationOptions: ({ navigation }) => ({
title: 'TODAY',
navigationState: navigation.state,
})
},
})
Read more about navigationOptions https://reactnavigation.org/docs/navigators/
Answer as of React Navigation v6
Depending on whether you want to trigger re-renders on value changes:
const state = navigation.getState();
or
const state = useNavigationState(state => state);
Reference:
https://reactnavigation.org/docs/use-navigation-state#how-is-usenavigationstate-different-from-navigationgetstate

Resources