0
votes

In my React Native App actually i need implement a simple Logout with navigation and redux, but i have an error when i dispatch the logout action from my Home page (on press Logout button), because in Home i render the current user and not exists in that moment.

enter image description here

I need dispatch Logout (delete current user) and after navigate to the Login page without render again Home or to know how implement componentWillMount before render.... How ?

my resum code: (Login works, i dont put the login code)

App.tsx

import React, {useState} from 'react';
import { NavigationContainer, StackActions } from '@react-navigation/native';
//... many imports

const store = createStore(rootReducer);

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

async componentDidMount() {
  //... some code
}

render(){
   if (this.state.loading) {
     return (
       <Root>
         <AppLoading />
       </Root>
    );
   }
   else {
     return (
       <Provider store={store}>
        <Root>
          <NavigationContainer>
            <Navigator/>
          </NavigationContainer>
        </Root>
       </Provider>          
     );
   }
  }
}

Navigator.tsx

import React from 'react'
import { createStackNavigator } from '@react-navigation/stack'
import {connect} from 'react-redux'
import Login from '../Views/Login'
import Home from '../Views/Home'

const Stack = createStackNavigator();

const Navigator= (props) =>{
   const {users} = props;
   return (
        <Stack.Navigator>
            {console.log('Navigator here, '+users.current)}
            {users.current ===null ? 
                (<>    
                    <Stack.Screen name="Login" component={Login}  options={{headerShown:false}}/>
                </>)
            :   (<>    
                    <Stack.Screen name="Home" component={Home} options={{headerShown:false}}/>
                </>)
            }
        </Stack.Navigator>
    );
 }

const mapStateToProps=(state)=>{
   const {users} = state;
   return {users};
}

export default connect(mapStateToProps)(Navigator);

Home.tsx (navigate after Logout ?)

import React from 'react'
import {Text, Container, Content, View, Button, Icon} from 'native-base'
import Styles from './Styles/HomeStyles'
import {connect} from 'react-redux'
import {Auth} from '../Services/Auth/AuthService'

const Home =(props) => {
   const {navigation, users} = props;
   return(
    <Container >
        <Content >      
            <View style={Styles.content}>                  
                <Text>Hi {users.current.id}</Text>
                <Button onPress={()=>Logout(users.current.id)} >
                    <Icon name='exit' />
                </Button>
            </View>
        </Content>
    </Container>
   );

   async function Logout(id:string){
      console.log('Function start');
      await Auth.LogoutUser(id, props);
      navigation.navigate('Login');
   }
 }

const mapStateToProps=(state)=>{
  const {users} = state;
  return {users};
}

export default connect(mapStateToProps)(Home);

UserReducer.tsx

const Initial_State={
   current:null
}

export function UserReducer(state=Initial_State, action:any){
   switch (action.type){
    case 'ADD_USER':
        return{
            ...state,
            current: action.user
        };
    case 'DELETE_USER':
        return{
            ...state,
            current: null
        };
    default:
        return state;
   }
}

AuthService.tsx (here the dispatch)

export const Auth={
   LoginUser,
   LogoutUser
}

async function LogoutUser(id: string, props: any){
  await props.dispatch({type : 'DELETE_USER', id});
}

async function LoginUser(AuthData:IAuthData, props: any){
  //...some code
}
2

2 Answers

1
votes

Your Home component expects to have a user object selected from the state. On that note, you should select just the specific object that you want, state.users.current, instead of selecting state.users and then looking at users.current.

We don't want Home to render unless we have a user, so we can useEffect to conditionally navigate away from the page. This means that we don't actually need to await logout as navigation occurs automatically whenever there is no user. The action gets dispatched --> redux state changes --> currentUser prop changes --> effect runs --> navigation occurs.

const Home = (props) => {
  const { navigation, currentUser } = props;

  function Logout(id) {
    // no async/await here
    // navigation is triggered when currentUser updates
    Auth.LogoutUser(id, props);
  }

  useEffect(() => {
    if (!currentUser) {
      navigation.navigate("Login");
    }
  }, [currentUser, navigation]);

  if (!currentUser) {
    // should only end up here momentarily while navigation is taking place
    return null;
  }
  
  return (
    <Container>
      <Content>
        <View style={Styles.content}>
          <Text>Hi {currentUser.id}</Text>
          <Button onPress={() => Logout(currentUser.id)}>
            <Icon name="exit" />
          </Button>
        </View>
      </Content>
    </Container>
  );
};

const mapStateToProps = (state) => {
  return {
    currentUser: state.users.current
  };
};

export default connect(mapStateToProps)(Home);
0
votes

In your case for now you can just handle the null state with users variable by changing the

  <Text>Hi {users.current.id}</Text>

To

<Text>Hi {users?.current?.id}</Text>

But in real app you should have to reset the complete app state and have to manage your conditions on that app state.

Example:

import { combineReducers } from 'redux';
import UsersReducer from './UsersReducer';

  const appReducer = combineReducers({
      users: UsersReducer,
     // other Reducers if there is any.
    });

 const rootReducer = (state, action) => {
   // When logout action is dispatched  reset the action.
   if (action.type === 'USER_LOG_OUT') {
     state = undefined; // or to initial state if {} you want any data without session 
    }
    return appReducer(state, action);
   };

export default rootReducer;