Bikram Nath
Bikram Nath

Reputation: 483

react navigation in react native, with conditional stack, and authentication

import React, { Component } from 'react'
import { Container, Content, Form, Item, Input, Label, Button,Text, Icon} from 'native-base'
import AsyncStorage from '@react-native-community/async-storage';
import authStore from '../store/authStore';
export default class Login extends Component {
    constructor(props){
        super(props);
        this.state={
            email:'',
            password:''
        }
    }
    handleLogin = async () =>{
        let requestObject = {
            email: this.state.email,
            password: this.state.password
        }
        authStore.userLogin(requestObject, response => {
            this.storeUserData(response.data.data);
            this.props.navigation.navigate('Home');
        })
    }
    storeUserData = async (value) => {
        try {
          const jsonValue = JSON.stringify(value)
          await AsyncStorage.setItem('@userData', jsonValue)
        } catch (e) {
          console.log(e);
        }
    }
    render() {
        return (
            <Container>
                <Content contentContainerStyle={{flex: 1, justifyContent:'center'}}>
                <Form style={{margin:10}}>
                    <Item rounded last style={{margin:10}}>
                    <Icon active type="FontAwesome" name='user' />
                    <Input placeholder='User Name' 
                    onChangeText={(email)=>this.setState({email})} 
                    value={this.state.email}/>
                    </Item>
                    <Item rounded last style={{margin:10}}>
                    <Icon active type="FontAwesome" name='key' />
                    <Input placeholder='Password'
                    secureTextEntry
                    onChangeText={(password)=>this.setState({password})} 
                    value={this.state.password}/>
                    </Item>
                    <Button rounded block style={{margin:10}} onPress={() => this.handleLogin()}>
                    <Text>Sign-In</Text>
                    </Button>
                </Form>
                </Content>
            </Container>
        )
    }
}

const AuthStack = createStackNavigator();
AuthStackScreen = () =>
      <AuthStack.Navigator>
        <AuthStack.Screen name="Login" component={Login} />
      </AuthStack.Navigator>
HomeStackScreen = () =>
  <HomeStackDrawer.Navigator>
    <HomeStackDrawer.Screen name="Home" component={HomeScreen}/>
    <HomeStackDrawer.Screen name="Form" component={FormScreen}/>
    <HomeStackDrawer.Screen name="Logout" component={Logout}/>
  </HomeStackDrawer.Navigator>
export default class App extends Component{
  constructor(props){
    super(props);
    this.state={
      isloggedIn:false
    }
    this.loginStatusCheck();
  }
  loginStatusCheck = async () =>{
    const userToken = await AsyncStorage.getItem('@accessToken');
    if (userToken) {
      this.setState({isloggedIn:true})
    } else {
      this.setState({isloggedIn:false})
    }
  }
  render(){
    return(
      <NavigationContainer>
        {this.state.isloggedIn ? <HomeStackScreen/> : <AuthStackScreen/>}
      </NavigationContainer>
    )
  }
}

This is my App.js, I am checking if the user is logged in or not, then loading the Navigation stack accordingly. I know the problem, If I Logout, I want to navigate to the sign-in component, but this.props.navigation.navigate('Login') gives error. because I am not returning the Login route. How to solve this issue? Also, when I Log in same issue, as the Login is not present in the stack. Thank you in advance

Included the login component

Upvotes: 1

Views: 8185

Answers (1)

Guruparan Giritharan
Guruparan Giritharan

Reputation: 16334

You will have to do some changes to fix this issue. Your problem is you are trying to access a screen in a navigation stack which is not there. And the biggest problem is using a state variable in App.js to handle the switch of navigation stacks. You can resolve this by maintaining the login status in a context in your application. You can update it from other screens as well. Once you update the login status you dont have to worry about the navigation and your condition in the App.js will manage that for you.

The code should be something like below. I have given a sample Login component which will update the context. You will have to switch to functional component. From your code i dont see any problem of doing that.

const AppContext = createContext({
  isloggedIn: {},
  setLoggedIn: () => {},
});

const Login = () => {
  const { setLoggedIn } = useContext(AppContext);

  return (
    <View>
      <Button onPress={() => setLoggedIn(true)} />
    </View>
  );
};

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isloggedIn: false,
      loading: true,
    };
    this.loginStatusCheck();
  }

  setLoggedIn = value => {
    this.setState({ isloggedIn: value });
  };

  loginStatusCheck = async () => {
    const userToken = await AsyncStorage.getItem('@accessToken');
    if (userToken) {
      this.setState({ isloggedIn: true, loading: false });
    } else {
      this.setState({ isloggedIn: false, loading: false });
    }
  };

  render() {
    if (this.state.loading) return <ActivityIndicator />;

    return (
      <AppContext.Provider
        value={{
          isloggedIn: this.state.isloggedIn,
          setLoggedIn: this.setLoggedIn,
        }}>
        <NavigationContainer>
          {this.state.isloggedIn ? <HomeStackScreen /> : <AuthStackScreen />}
        </NavigationContainer>
      </AppContext.Provider>
    );
  }
}

Hope this helps.

Upvotes: 2

Related Questions