Navigating between components reloads all components in navigator routes - android

I've read somewhere that when you use any navigator from the react-navigation package and if you implement redux; every component in the navigators' routes will be reloaded (i.e componentWillRecieveProps)
However, I've got two pages where the user can login
export const MainScreenTabNavigator = TabNavigator({
Start: {
screen: Start,
navigationOptions: {
title: 'Start',
},
},
},{
tabBarPosition: "bottom",
tabBarOptions: {
activeTintColor: '#222',
labelStyle: {
color: '#ddd',
},
style: { backgroundColor: '#333' },
}
});
export const AppNavigator = StackNavigator({
Main: {
screen: MainScreenTabNavigator, // Nested tab navigator
},
Login: {
screen: Login,
navigationOptions: {
title: 'Aanmelden',
}
},
Camera: {
screen: AppCamera,
navigationOptions: {
title: 'Camera',
}
}
}, {
mode: 'modal',
headerMode: 'none',
});
The login screen is initially shown to the user. It has a form where the user can enter it's credentials manually and a button that navigates to the camera where the user can scan a QR code with login credentials.
In both cases the user dispatches a login action.
Both the login page and the camera page listen to the same prop changes:
componentWillReceiveProps(nextProps) {
if (nextProps.account.loginSuccess) {
this.props.navigation.dispatch(NavigationActions.navigate({ routeName: 'Start' }));
} else {
if (nextProps.account.loginError) {
Toast.showLongBottom(nextProps.loginError);
}
}
this.setState({spinnerVisible: false});
}
The app successfully navigates to 'Start' but when it does both the Login and Camera page are reloaded, causing an infinite loop into componentWillReceiveProps and infinitely navigates to 'Start' over and over again.
This is my navigation reducer:
function nav(state = initialNavState, action) {
const nextState = AppNavigator.router.getStateForAction(action, state);
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
}
What can I do to prevent this behavior?

Well,
As a temporary solution I introduced another boolean into the nav state:
function nav(state = initialNavState, action) {
let nextState = null;
if (action.type === 'Navigation/NAVIGATE' && action.routeName === 'Start') {
nextState = {...AppNavigator.router.getStateForAction(action, state), authenticated: true};
} else {
nextState = AppNavigator.router.getStateForAction(action, state);
}
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
}
I use authenticated to check if the login or camera component should navigate to start after logging in.
It works but it still feels like I'm missing something.

Related

React Navigation 3: Back button in Android doesn't back to previous screen

I am upgrading my router configuration of my React Native app using React Navigation 3 and many things has been improved now but I don't understand why when I press the back button in Android, is not sending me to the previous view and instead is sending me to the Home one.
My routes
const drawerConfig = {
initialRouteName: 'Home',
contentComponent: SideMenu,
drawerWidth: width,
}
const MainDrawerNavigator = createDrawerNavigator(
{
Home: {
screen: Home,
},
Company: {
screen: Company,
},
Gifts: {
screen: Gifts,
},
Jobs: {
screen: Jobs,
},
Job: {
screen: Job,
},
Contact: {
screen: Contact
}
},
drawerConfig,
);
const InitialStack = createStackNavigator(
{
Menu: {
screen: Menu,
path: 'menu/',
}
},
{
initialRouteName: 'Menu',
headerMode: 'none',
}
);
const SwitchNavigator = createSwitchNavigator(
{
Init: InitialStack,
App: MainDrawerNavigator,
},
{
initialRouteName: 'Init',
}
);
const AppContainer = createAppContainer(SwitchNavigator);
export default AppContainer;
If I do this: Open Drawer, open Jobs then press in a job button to load the Job view the flow is working well but If I press the back button in the Job view is not showing the Jobs but the Home.
I am navigation using this.props.navigation.navigate('...') because the push is not working.
Do you know why?
I am using react-navigation 3.5.1 and react-native 0.59.3
I just figured out.
I was not writing the right configuration for my routes because if I wanted to back from Job to Jobs instead to Home, a stack was necessary for every "stack" of views I needed, so now they can work exactly how I want, the pop is working great without using the back handler event.
Like this:
// Jobs stack
const JobsStack = createStackNavigator(
{
JobList: {
screen: Jobs,
},
Job: {
screen: Job,
},
},
{
headerMode: 'none',
}
);
// Main drawer
const MainDrawerNavigator = createDrawerNavigator(
{
...
Jobs: JobsStack,
Contact: {
screen: Contact
}
...
},
drawerConfig,
);
Thank you to all the people that helped me :)
You can import BackHandler from 'react-native' and can use the code like below in your screen class from where you want to go back:
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = () => {
this.props.navigation.goBack();
return true;
};

React Native Android Hardware Back button not working properly

LoginScreen.js
this.props.navigator.push({
screen: "auxxa.LandingScreen",
passProps: { login: true },
overrideBackPress: true,
navigatorStyle: {
navBarHidden: true
}
});
LandingScreen.js
constructor(props) {
super(props);
this.handleBackButtonClick = this.handleBackButtonClick.bind(this);
// this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
this.state = {
size: { width, height },
tileData: null,
isLoading: true,
user_id: null,
refetching: false,
access_token: null
};
}
componentWillMount() {
BackHandler.addEventListener(
"hardwareBackPress",
this.handleBackButtonClick
);
}
handleBackButtonClick() {
console.log("check login " + this.props.login);
if (this.backPressed && this.backPressed > 0) {
if (this.props.login) {
console.log("login");
RNExitApp.exitApp();
} else {
console.log("root");
this.props.navigator.popToRoot({ animated: false });
return false;
}
}
this.backPressed = 1;
this.props.navigator.showSnackbar({
text: "Press one more time to exit",
duration: "long"
});
return true;
}
componentDidMount() {
BackHandler.removeEventListener(
"hardwareBackPress",
this.handleBackButtonClick
);
}
I used react-native-navigation from Wix for my app nevigation purpose.Here I have attached login screen and landing screen.after successful login app navigate to landing screen.after that I click back button It will return to login screen.I need to avoid that.How can I do that thing? I tried to exit from the app.But it also not working properly.
Please help me if some one know this.Thanks in advanced.
Use this call in handleBackButtonClick function and why are you removing the listener in componentDidMount ?
this.props.navigator.resetTo({ screen: 'example.ScreenThree'})
.
Uncomment this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); on your constructor to listen to navigation events
and add the navigationEvent listener method
onNavigatorEvent(event: NavigatorEvent) {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'skill_information') {
// Add here whatever you would like to do (this.handleBackButtonClick() for example)
}
}

Prevent user from returning to previous screen StackNavigator

I want to do a simple task: after a successful login redirect the user to his home page. I use react-navigation's StackNavigator for that purpose:
// App.js
class App extends Component {
render() {
return (<RootStack />);
}
}
const RootStack = createStackNavigator(
{
Login: { screen: Login, navigationOptions: { header: null }},
Home: { screen: Home, navigationOptions: { header: null }}
},
{
initialRouteName: 'Root'
}
)
How do I prevent user from returning to Login screen after login? To prevent use back button in android, I would use this one:
// Home.js
import React, { Component} from 'react';
import { ... , BackHandler } from 'react-native';
class Home extends Component {
constructor(props) {
super(props);
BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid);
}
onBackButtonPressAndroid = () => {
return true;
}
}
But doing this way, I disable back button at all. Is there another way to achieve the goal?
The react native documentation has an excellent page on how to make an authentication flow.
You may not be familiar with SwitchNavigator yet. The purpose of SwitchNavigator is to only ever show one screen at a time. By default, it does not handle back actions and it resets routes to their default state when you switch away. This is the exact behavior that we want from the authentication flow: when users sign in, we want to throw away the state of the authentication flow and unmount all of the screens, and when we press the hardware back button we expect to not be able to go back to the authentication flow. We switch between routes in the SwitchNavigator by using the navigate action. You can read more about the SwitchNavigator in the API reference.
According to the documentation, this SwitchNavigator is implemented like this:
import { createSwitchNavigator, createStackNavigator } from 'react-navigation';
// Implementation of HomeScreen, OtherScreen, SignInScreen, AuthLoadingScreen
// goes here.
const AppStack = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
export default createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
);
So, to achieve what you are looking for, you would want to change your RootStack to the following (note i have not tested this code):
const RootStack = createSwitchNavigator(
{
Loading: ,//put your loading screen here
Auth: { screen: Login, navigationOptions: { header: null }},
App: { screen: Home, navigationOptions: { header: null }}
},
{
initialRouteName: 'Loading'
}
)
Then, in your loading screen, you would fetch whatever state is needed to determine if the user is already signed in and you would call either this.props.navigation.navigate('App'); to skip the login screen and take the user directly to your app, or this.props.navigation.navigate('Auth'); to send your user to the login page. the SwitchNavigator automatically handles disabling back navigation for you.

How to finish current component while navigating to next component in react native

Hi I am trying to navigate to next component using navigate function. I am using react-navigation for the navigation among multiple components.
Suppose I have index.android.js and DashboardScreen.js component. I am trying to navigate to DashboardScreen.js component from index component.
It is navigating but index component always retain in component stack. when I press back then it opens index.android.js which should not be. Does anyone know how to manage this in react-native. In Android, finish() works for this.
navigate("DashboardScreen");
When I am navigating from SplashScreen to EnableNotification then SplashScreen should be destroyed, if I am navigating from EnableNotification to CreateMessage then EnableNotification should be destroyed and if I am navigating from CreateMessage to DashboardScreen then CreateMessage should be destroyed. As of now no component is being destroyed.
index.android.js
class SplashScreen extends Component {
render() {
if (__DEV__) {
console.disableYellowBox = true;
}
const { navigate } = this.props.navigation;
AsyncStorage.getItem("#ProductTour:key").then(value => {
console.log(value);
if (value) {
navigate("DashboardScreen");
}
});
return (
....
);
}
}
const App = StackNavigator(
{
Splash: {
screen: SplashScreen,
navigationOptions: {
header: {
visible: false
}
}
},
EnableNotification: {
screen: EnableNotificationScreen,
navigationOptions: {
header: {
visible: false
}
}
},
CreateMessage: {
screen: CreateMessageScreen,
navigationOptions: {
header: {
visible: false
}
}
},
DashboardScreen: {
screen: DashboardScreen,
navigationOptions: {
header: {
visible: false
}
}
}
},
{
initialRouteName: "Splash"
}
);
Just use 'replace' in place of 'navigate'
this.props.navigation.replace('Your Next Component Name')
First of all, using AsyncStorage in an a synchronous function (most especially a lifecycle one) is such a bad idea. You should typically keep ASyncStorage to places in your folder / app structure that make sense for where you access/keep data but since that's not the question I will just mention it quickly here...
Basically you are asking to navigate once the ASync method completes itself based on EVERY render... Those new to RN should know that an awful lot of things can cause a render to fire. Some cases, the render function can fire (I have seen this many times before) 10 or more times before finalizing the last render. This means you would have fired that ASyncStorage method 10 times... definitely something to think about when implementing this stuff. So more or less, the .then(); part of the AsyncStorage function is firing long after the render has already finished doing it's thing. If it was a reasonable approach to use I would say to put the return part of the render function inside of the .then((value) => { return ( ... ); });. But this is an even worse idea. Basically you need the right lifecycle method here and it's NOT the render method.
Anyway, since I have never used this component library before I can only help nudge you in the right direction so here goes... These docs on their webpage seem to say that you need a reference to the props navigator passed down to the component in which you are using it. So if you created the navigator in this class, you would use this.refs.whateverYouNamedTheNavigatorReference.navigate('SomeItemName'). If you are in the class that has been passed this navigator as a prop, you use this.props.passNavigatorPropName.navigate('SomeItemName'). I see you are using variable deconstruction to get the navigate callback but I would caution on doing this, this way because I have seen it cause errors by grabbing an old version of the navigate function or its parent reference by accident and causing a cascading error effect.
Also, if you are going to be using ASyncStorage in a component file (again, would recommend putting this in a component/class where your data is accessed throughout the app...) and you are going to use it to decide the app should navigate forwards/backwards... definitely remove it from the render function and put it in maybe the constructor, componentWillReceiveProps, componentDidReceiveProps or componentWillUpdate lifecycle functions. That way it fires based on an update, a new passed prop obj or one time as the component is built. Anything is better than firing it every single render.
Lastly, I do not know what you have setup for your StackNavigator route stack object but you would need to have the keyword you used "DashboardScreen" in there pointing to an actual component that has been imported properly. The "DashboardScreen" keyword most likely would connect in your StackNavigator object to some component import like so...
import Dashboard from '../Views/DashboardScreenView';
StackNavigator({
DashboardScreen: {
screen: Dashboard,
path: 'dashboard/:main',
navigationOptions: null,
},
});
There is a simple way here: use "replace" (reference link repleace in navigation ,For example, you are at the screen "Login" ,
and you want to move to screen "Home", insert this code in screen "Login"
<TouchableOpacity onPress={() => { this.login() }}>
<Text}>Click me to Login</Text>
</TouchableOpacity>
and method login:
login(){
this.props.navigation.replace('Home')
}
Screen "Login" will be replaced by "Home", in Android, press Back Button =>app exit, no back screen "Login"
Based on your requirement, i suggest following setup:
SplashNavigator.js
const SplashNavigator = StackNavigator({
Splash: {
screen: SplashScreen,
navigationOptions: {
header: {
visible: false
}
}
}
});
AppNavigator.js
const AppNavigator = StackNavigator(
{
EnableNotification: {
screen: EnableNotificationScreen,
navigationOptions: {
header: {
visible: false
}
}
},
CreateMessage: {
screen: CreateMessageScreen,
navigationOptions: {
header: {
visible: false
}
}
},
Dashboard: {
screen: DashboardScreen,
navigationOptions: {
header: {
visible: false
}
}
}
},
{
initialRouteName: "EnableNotification"
}
);
In your index.android.js, you will render the SplashNavigator.
The SplashNavigator will render the SplashScreen. It has initial state value isReady set to false, so it will render a loading text until the #ProductTour:key value from AsyncStorage is loaded (AsyncStorage is async function, u should not put it in your render function). It will then render your AppNavigator and render your EnableNotification as initial route.
class SplashScreen extends Component {
constructor() {
super(props);
this.state = {
isReady: false,
}
}
componentDidMount() {
AsyncStorage.getItem("#ProductTour:key").then(value => {
console.log(value);
// you will need to handle case when `#ProductTour:key` is not exists
this.setState({
isReady: true,
});
});
}
render() {
const { isReady } = this.state;
return (
<View style={{flex: 1}}>
{
isReady ?
<AppNavigator />
: <Text>Loading</Text>
}
</View>
);
}
}
Then on EnableNotificationScreen and CreateMessageScreen, change your navigate route function to use NavigationActions.reset from doc
Example:
import { NavigationActions } from 'react-navigation';
handleOnPressButton = () => {
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: "CreateMessage" })
]
});
this.props.navigation.dispatch(resetAction);
}
Yes in react native you can finish the current screen before navigating to new screen with the help of NavigationActions . Please refer this link -
http://androidseekho.com/others/reactnative/finish-current-screen-on-navigating-another-in-react-native/
SplashNavigator.js
const SplashNavigator = StackNavigator({
Splash: {
screen: SplashScreen,
navigationOptions: {
header: null}
}
}
});
Import StackActions and NavigationActions from react-navigation.
import { StackActions, NavigationActions } from 'react-navigation';
below code for performing Action
navigateToHomeScreen = () => {
const navigateAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: "HomeScreen" })],
});
this.props.navigation.dispatch(navigateAction);
}

React Navigation + Redux close App on Back Pressed

After converting the app to redux, my react-navigation got some problem. Previously, before integrating with redux, when I press back button (Physical button) react-navigation back to the previous screen. After integrating with redux, the back button will close the app. But, it's still working with goBack() function.
I'm following the guide: https://reactnavigation.org/docs/guides/redux
And read some code from here : https://github.com/react-community/react-navigation/tree/master/examples/ReduxExample
And, this is my Navigator configuration
export const AppNavigator = StackNavigator(
{
Home: { screen: HomeScreen },
ChatDetail: { screen: ChatDetail },
PulsaDetail: { screen: PulsaDetailScreen },
Pulsa: { screen: Pulsa }
},
{
headerMode: 'none',
}
)
class AppWithNavigation extends Component {
render(){
return(
<AppNavigator navigation={ addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})} />
)
}
}
const mapStateToProps = (state) => ({
nav: state.nav
})
export default connect(mapStateToProps)(AppWithNavigation)
EDIT: It's can be done with manual handle & dispatch back action, but it's can't do it automaticlly? just like before using redux?
BackHandler.addEventListener('hardwareBackPress',() => {
this.props.goBack()
return true
})
After post Github issue in react-navigation repository, I got the answer.
Should add manually the back listener on top of screen / component
// App.js
import { BackAndroid } from 'react-native'
// [...]
componentDidMount() {
BackAndroid.addEventListener('backPress', () => {
const { dispatch, nav } = this.props
if (shouldCloseApp(nav)) return false
dispatch({ type: 'Back' })
return true
})
}
componentWillUnmount() {
BackAndroid.removeEventListener('backPress')
}
// [...]
https://github.com/react-community/react-navigation/issues/2117
https://github.com/react-community/react-navigation/issues/117
UPDATE:
https://facebook.github.io/react-native/docs/backhandler

Categories

Resources