react native navigation - componentDidMount() fired twice

Viewed 2971

I am new to React Native. I am trying to build an app which has a Splash screen that would later navigate to Login screen if a user has not been authenticated or the Main screen if the user is authenticated. This is done using this.props.navigation.navigate() The problem is that the Splash component would be mounted twice. I checked this by printing inside componentDidMount() of Splash. Because of this, the Login/Main screen enters twice, which looks very unpleasant. Is there any way to fix this? Also, I want to add some delay when the screen changes from Splash to Login or Main using setTimeout(). Anyway to go about doing this? Here's my code:

index.js

import React from 'react';
import { createStore, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import { persistStore } from 'redux-persist';
import reduxThunk from 'redux-thunk';
import reducers from './src/reducers';

import { StyleSheet } from 'react-native';
import LoginScreen from './src/components/Login/LoginScreen';
import Splash from './src/components/Login/Splash';
import Navigation from './src/components/Navigation/Navigation';
import { Font } from 'expo';
import {
  createStackNavigator
} from 'react-navigation';

const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
const persistor = persistStore(store);

export default class App extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      fontLoaded: false,
      currentScreen: 'Splash',
    };
    setTimeout(() => this.setState({currentScreen: 'Login'}), 2000);
  }

  async componentDidMount() {
    await Font.loadAsync({
      'Quicksand': require('./assets/fonts/Quicksand-Regular.ttf'),
      'Quicksand-Medium': require('./assets/fonts/Quicksand-Medium.ttf'),
      'Quicksand-Bold': require('./assets/fonts/Quicksand-Bold.ttf'),
    });
    this.setState({ fontLoaded: true });
  }

  render() {
    const MainNavigator = createStackNavigator({
      Splash: { screen: Splash },
      Main: { screen: Navigation },
      Login: { screen: LoginScreen },
    })
    if (this.state.fontLoaded)
      return (
        <Provider store={store}>
          <MainNavigator></MainNavigator>
        </Provider>
      )
    else return null;
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Splash.js

import React from 'react';
import { StyleSheet, Text, View, ImageBackground, Image, Button } from 'react-native';
import bgImage from '../../../assets/images/login-background2.png';
import logo from '../../../assets/images/app-logo.png';

import { connect } from 'react-redux';
import { checkAuth } from '../../actions/auth.actions';

class Splash extends React.Component {
  static navigationOptions ={
    header: null
  }
  constructor(props){
    super(props);
    this.state = {
      stillLoading: true,
    }
  }

  componentDidMount() {
    this.props.checkAuth();
  }

  render() {
    if (this.props.authState.isLoginPending)
      return (
        <ImageBackground source={bgImage} style={styles.backgroundContainer}>
              <View style={styles.logoContainer}>
                  <Image source={logo} style={styles.logo}></Image>
                  <Text style={styles.logoText}> Welcome to HealthScout</Text>
              </View>
        </ImageBackground>
      );
    else if (this.props.authState.isLoginSuccess){
      setTimeout(() => this.props.navigation.navigate('Main'));
      return null;
    }
    else{
      setTimeout(() => this.props.navigation.navigate('Login'));
      return null;
    }
  }
}

const mapStateToProps = state => {
  return {
    authState: state.authState
  }
}

const mapDispatchToProps = dispatch => {
  return {
    checkAuth: () => dispatch(checkAuth()),
  }
}

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

const styles = StyleSheet.create({
  backgroundContainer: {
    flex: 1,
    alignItems: 'center',
    width: null,
    height: null,
    justifyContent: 'center',
  },
  logoContainer: {
    alignItems: 'center',
  },
  logo: {
    width: 110,
    height: 149,
  },
  logoText: {
    color: '#fff',
    fontSize: 40,
    fontFamily: 'Quicksand',
    opacity: 0.7,
    marginTop: 20,
    marginBottom: 10,
    textAlign: 'center',
  },
});
1 Answers

Solution

Take out the createStackNavigator from render. It is better way wrapping screens above App class.

const MainNavigator = createStackNavigator({
  Splash: { screen: Splash },
  Main: { screen: Navigation },
  Login: { screen: LoginScreen },
})

export default class App extends React.Component {
...

Why?

render is run repeatedly depends on various conditions as changing state, props and so on.
And your code looks making multiple components with createStackNavigation in render. Take out :)

p.s If you want to wait loading fonts before show home screen, just change to home screen from splash screen after loaded fonts. Thus, the better way is loading fonts in SplashScreen and do what you want.

Related