React-native-navigation: Single Screen to Tab Based App

Created on 3 Apr 2018  路  15Comments  路  Source: wix/react-native-navigation

Issue Description

Switching from single screen to tab based app is not happening in ios, whereas same code working like charm in android

Code

import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  View,
  ImageBackground,
  Dimensions,
  Image,
  TouchableOpacity,
  Alert,
  AsyncStorage
} from 'react-native';
import { strings } from '../locales/i18n';
import { Navigation } from 'react-native-navigation';
import { MKTextField } from 'react-native-material-kit';
import axios from 'axios';
import Spinner from 'react-native-loading-spinner-overlay';
import * as userActions from '../src/actions/userActions'
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Appurl from './../config';
import Validation from './../src/utils/Validation.js';
import OneSignal from 'react-native-onesignal';

class login extends Component {

  constructor(props) {
    super(props);
    this.state = {
      emailPhone : '',
      visible : false,
      userid : '',
      password : '',
      show_password: true,
    }
  }

  static navigatorStyle = {
    navBarHidden : true,
  }
  componentWillUnmount() {
    let {actions} = this.props;
    actions.toggleButton(false);
  }

  showPassword = () => {
    let {show_password} = this.state;
    if(show_password) {
      this.setState({show_password:false})
    }
    else {
      this.setState({show_password:true})
    }
  }

  validationRules= () => {
      return [
          {
              field: this.state.password,
              name: 'Password',
              rules: 'required|no_space|min:6|max:15'
          },
      ]
  }

  back = () => {
    let {actions} = this.props;
    actions.toggleButton(false);

    this.props.navigator.pop();
  }

  loginPassword = async() => {
    let {emailPhone, visible, password} = this.state;

    let validaton= Validation.validate(this.validationRules());

    if(validaton.length != 0) {
    return Alert.alert(
    '',
    validaton[0],
    [
      {
        text: strings('globalValues.AlertOKBtn'),
        onPress: ()=> {

        }
      }
    ],
    { cancelable: false }
  );
}

    else {
    this.setState({visible : true});
    let {actions} = this.props;
    actions.getLoginField(emailPhone);
    OneSignal.sendTag("phone", emailPhone);
    let values = {'authfield' : emailPhone, 'password' : password, 'langaugeType' : this.props.user.lang};
    console.log(values);
    axios.post(`${Appurl.apiUrl}loginUser`, values)
    .then((response) => {
      console.log(response)
      this.setLoginPassword(response)
    }).catch((error) => {
      if(error.response.data.success == 0) {
        Alert.alert(
            '',
            error.response.data.msg,
            [
                {
                        text: strings('globalValues.AlertOKBtn'),
                        onPress: () => {
                        this.setState({visible: false});
                } }
            ],
            { cancelable: false }
          );
        }
      })
    }
  }

  forgetPassword = () => {
    this.props.navigator.push({
      screen : 'forgotPassword'
    })
  }

  setLoginPassword = async(response) => {
    console.log(response);
    let {visible} = this.state;
    let {actions} = this.props;
    console.log(response);
    // let userid = response.data.userId;
    // actions.getLoginUserId(userid);
    try {
    this.setState({visible: false});
    let details = {'image': response.data.Profilepicurl , 'name': response.data.name , 'id': response.data.userId, 'email' : response.data.email}
    await AsyncStorage.setItem('user', JSON.stringify(details));
    Navigation.startTabBasedApp({
      tabs: [
        {
          label: strings('globalValues.Tab1'),
          screen: 'famcamHome',
          icon: require('./../Images/ic_home_outline.png'),
          selectedIcon: require('./../Images/ic_home_filled.png'), // local image asset for the tab icon selected state (optional, iOS only. On Android, Use `tabBarSelectedButtonColor` instead)
          title: 'Home',
        },
        {
          label: strings('globalValues.Tab2'),
          screen: 'orders',
          icon: require('./../Images/ic_clipboards_outline.png'),
          selectedIcon: require('./../Images/ic_clipboards_filled.png'),
          title: 'Orders',
        },
        {
          label: strings('globalValues.Tab3'),
          screen: 'profile',
          icon: require('./../Images/ic_profile_outline.png'),
          selectedIcon: require('./../Images/ic_profile_filled.png'),
          title: 'Profile',
        },
  ],
  tabsStyle: {
    tabBarButtonColor: '#C54C72',
    tabBarLabelColor: '#C54C72',
    tabBarSelectedButtonColor: '#C54C72',
    tabBarBackgroundColor: 'white',
    initialTabIndex: 0,
    tabBarTextFontFamily: this.props.user.lang=='ar'?'AssawtDecorative':null
  },
  appStyle: {
    tabBarSelectedButtonColor: '#C54C72',
    tabFontFamily: this.props.user.lang=='ar'?'AssawtDecorative':null
  },
    })
  }
  catch(error) {}
}

  render() {
      let {emailPhone, password, show_password} = this.state;
      return (
        <View style={{flex:1, marginHorizontal: 24}}>

          <Spinner visible={this.state.visible} color='#8D3F7D' tintColor='#8D3F7D' animation={'fade'} cancelable={false} textStyle={{color: '#FFF'}} />

          <View style={{flex: 0.1, justifyContent: 'center'}}>
            <TouchableOpacity hitSlop = {{top:7, left:7, bottom:7, right:7}} style={{height: 20, width:24, justifyContent: 'center'}} onPress={() => {this.back()}}>
              <Image source={require('./../Images/icBack.png')} style={{height: 14, width:18}}/>
            </TouchableOpacity>
          </View>
          <View style={{flex:0.09, justifyContent: 'flex-start'}}>
            <Text style = {{fontSize: 24, lineHeight: 32, fontWeight: this.props.user.lang=='en'?'bold':null, color: '#000000', fontFamily: this.props.user.lang=='ar'?'AssawtDecorative':null}}>{strings('login.login')}</Text>
          </View>
          <View style={{flex:0.08}}>
            <Text style = {{fontSize: 14, lineHeight: 20, color: '#474D57', fontFamily: this.props.user.lang=='ar'?'AssawtDecorative':null}}>{strings('login.heading')}</Text>
          </View>
          <View style = {{flex:0.15}}>
              <MKTextField
                placeholder = {strings('login.placeholder')}
                ref="emailPhone"
                placeholderTextColor='#AAAFB9'
                floatingLabelEnabled
                keyboardType = "email-address"
                returnKeyType = "next"
                textInputStyle = {{fontSize: 16, lineHeight: 24, color: '#474D57', textAlign: this.props.user.lang=='en'?'left':'right'}}
                style = {{marginTop:10}}
                underlineSize={1}
                highlightColor='#474D57'
                tintColor='#C2567A'
                autoCorrect={false}
                autoCapitalize= 'none'
                onChangeText = {(emailPhone) => this.setState({emailPhone})}
                onSubmitEditing = {(event) => {this.refs.password.focus()}}
              />
          </View>

          <View style = {{flex:0.12, flexDirection: 'row'}}>
            <MKTextField
              placeholder = {strings('login.placeholder2')}
              ref="password"
              placeholderTextColor='#AAAFB9'
              floatingLabelEnabled
              password={show_password}
              keyboardType = "default"
              returnKeyType = "done"
              textInputStyle = {{fontSize: 16, lineHeight: 24, color: '#474D57', textAlign: this.props.user.lang=='en'?'left':'right'}}
              style = {{marginTop:10, flex:0.99}}
              underlineSize={1}
              highlightColor='#474D57'
              tintColor='#C2567A'
              autoCorrect={false}
              autoCapitalize= 'none'
              onChangeText = {(password) => {this.setState({password})}}
            />
          </View>

          <View style={{flex: 0.1, justifyContent: 'flex-start'}}>
            <TouchableOpacity style={{justifyContent: 'flex-start'}} onPress = {() => {this.forgetPassword()}}>
              <Text style={{fontSize: 14, lineHeight: 16, color: 'black', fontWeight: this.props.user.lang=='en'?'bold':null, textAlign: this.props.user.lang=='en'?'left':'right', fontFamily: this.props.user.lang=='ar'?'AssawtDecorative':null}}> {strings('login.forgotPassword')} </Text>
            </TouchableOpacity>
          </View>
          <View style={{flex: 0.1, justifyContent: 'center'}}>
            <Text style={{fontSize: 13, color: '#9B9B9B', lineHeight: 16, textAlign: 'center', fontFamily: this.props.user.lang=='ar'?'AssawtDecorative':null}}>* {strings('login.NotificationText')}</Text>
          </View>
          <View style = {{flex:0.1,alignItems : 'flex-end'}}>
            <TouchableOpacity activeOpacity={0.5} onPress = {() => {this.loginPassword()}}>
              <Image source = {require('./../Images/fab.png')} style={{height: 56, width: 56}} />
            </TouchableOpacity>
          </View>
        </View>
      )
    }


}

function mapStateToProps(state, ownProps) {
    return {
        user: state.user
    };
  }

  function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(userActions, dispatch)
    };
  }

  export default connect(mapStateToProps, mapDispatchToProps)(login);

Steps to Reproduce / Code Snippets / Screenshots

It is working perfect in Android, but in ios if i directly call starttabbased onPress it works but like i used in the code the screen does not change, but the logs did of tabs

screen shot 2018-04-03 at 4 26 31 pm
screen shot 2018-04-03 at 4 26 56 pm
screen shot 2018-04-03 at 4 27 17 pm


Environment

  • React Native Navigation version: 1.1.309
  • React Native version: 0.51.0
  • Platform(s) (iOS, Android, or both?): IOS
  • Device info (Simulator/Device? OS version? Debug/Release?): All

Most helpful comment

I was FINALLY able to fix the issue after a long time debugging.
The problem and solution were very similar to @rf1804's.

TL/DR

Avoid making UI changes in the current screen at the same moment you are transitioning to another root app.

Details

I had this scenario:

  1. Login Screen (single screen)
  2. It opens a modal to login with phone
  3. The phone modal dispatches the api request
  4. On success, dispatch LOGIN_SUCCESS action
  5. On LOGIN_SUCCESS, change to tab based app

    • _This step was not happening for one person on the team (that uses an iPhone X). It worked fine on emulator and on my iPhone 6S._

The issue was because the LOGIN_SUCCESS was not only dispatching the root app change, but it was causing a lot of UI updates, specially inside the login phone modal. This was interrupting the root change.

So it was fixed by preventing these UI changes. In my case, as it was the phone modal that were making UI changes, I had to make sure it was completely closed by calling await navigator.dismissModal() (the await is essential) before dispatching LOGIN_SUCCESS.

PS: The screens from the tab based app were created, their componentDidMount were called, etc, it just wasn't becoming visible. The previous single screen app wasn't destroyed / stayed on top of it.

All 15 comments

@guyca any help on this??

Same issue is happening with lightbox on ios, after executing axios request it does not close, and if i comment the axios part and call directly onPress it works

i've tried every way but nothing working, please help
@guyca

Any help guys, i'm really stuck on this
@guyca @dvxme @DanielZlotin @talkol @gran33

Hey @rf1804
I'm not aware of any issues with calling startTabBasedApp after startSingleScreenApp. I see you wrapped the call with try catch, is an error thrown? Did you try printing it?
did you check the native logs? Did you try to debug the native iOS code?
I really can't help you much here.

@guyca there are no errors, i've included the screenshots of logs, there are no errors, the thing is everything happens correctly but the screen does not get changed, even after the action no buttons functionality works, it's like everything happened but nothing showing and this problem is happening in ios only in android working fine

+1 same issue here

while i was playing it around to find some solution i found out the problem got solved if i do not show the spinner, i'm thinking setting state for spinner interrupts the screen rendering only in ios case of tab based app

A testflight user is facing this (the app is not changing from the single screen to the tabbed screen).
I am not able to reproduce.

The logs show that the tab screens loaded, but then all of a sudden it we back to the single screen one.

@rf1804 do you have a repo with this issue isolated for easy reproduction?

I was FINALLY able to fix the issue after a long time debugging.
The problem and solution were very similar to @rf1804's.

TL/DR

Avoid making UI changes in the current screen at the same moment you are transitioning to another root app.

Details

I had this scenario:

  1. Login Screen (single screen)
  2. It opens a modal to login with phone
  3. The phone modal dispatches the api request
  4. On success, dispatch LOGIN_SUCCESS action
  5. On LOGIN_SUCCESS, change to tab based app

    • _This step was not happening for one person on the team (that uses an iPhone X). It worked fine on emulator and on my iPhone 6S._

The issue was because the LOGIN_SUCCESS was not only dispatching the root app change, but it was causing a lot of UI updates, specially inside the login phone modal. This was interrupting the root change.

So it was fixed by preventing these UI changes. In my case, as it was the phone modal that were making UI changes, I had to make sure it was completely closed by calling await navigator.dismissModal() (the await is essential) before dispatching LOGIN_SUCCESS.

PS: The screens from the tab based app were created, their componentDidMount were called, etc, it just wasn't becoming visible. The previous single screen app wasn't destroyed / stayed on top of it.

I do think this is a bug though, just a possibly rare one.

The root change should always work, nothing should be able to interrupt it.
_Or: the previous root app should always be destroyed, nothing should keep it mounted/visible after a root change_

Maybe this should be reopened? cc @guyca

@brunolemos yes the problem is if ui changes occur, previous root app does not get destroyed and it causes the problem, @guyca may be it can be handled for future uplifting of wix navigation.

Interesting.. @guyca try to reproduce this in e2e in v2?

Agree it needs to be fixed. For me I use rn-modal to show a loading indication as a overlay across the app. And most of the time it will show up before transition, right now I have to rewrite all of them to solve this 馃槩

We're also seeing this issue of the root app not being changed. I'll see if I can put together a basic simple app to replicate.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

viper4595 picture viper4595  路  3Comments

ghost picture ghost  路  3Comments

birkir picture birkir  路  3Comments

switchtrue picture switchtrue  路  3Comments

yayanartha picture yayanartha  路  3Comments