React navigation doesn't change navigation screen after redux state changes

Viewed 941

Navigation does not change to MainScreen even after redux state changes. I have verified that authState changes from {"isAuthenticated": false, "isLoading": true, "token": null} to {"isAuthenticated": true, "isLoading": false, "token": "some_token"}, but the navigation page stays at login page (inside LandingNavigator) instead of going to MainNavigator.

AppNavigation.js

const Navigator = () => { 

  var [authStat, setAuthStat] = useState({})

  useEffect(()=> {
    var authState = store.getState().auth
    setAuthStat(authState)
  }, [authStat])
  console.log(authStat);


  if(authStat.isLoading){
    return(
      <Stack.Navigator>
      <Stack.Screen
        options={{headerShown: false}}
        name="Splash"
        component={ShowSplash}
      />
      </Stack.Navigator>
    )
  }
  else{
    return(
      <Stack.Navigator>
        { authStat.isAuthenticated ?
          (<Stack.Screen
            options={{headerShown: false}}
            name="Main"
            component={MainNavigator}
          />)
          :
          (<Stack.Screen options={{headerShown: false}} name="Landing" component={LandingNavigator} />)
          }
      </Stack.Navigator>
    )
  }

};

AuthAction.js

export function loginRequest() {
    return {
        type: "LOGIN_REQUEST",
    };
}

export function loginSuccess(data) {
    return {
        type: "LOGIN_SUCCESS",
        payload: data
    };
}

export function loginFailure(data) {
    return {
        type: "LOGIN_FAILURE",
        payload: data
    };
}

export function restoreToken(data) {
    return {
        type: "RESTORE_TOKEN",
        payload: data
    };
}

export function logOut() {
    return {
        type: "LOGOUT",
    };
}

AuthReducer.js

/* eslint-disable comma-dangle */
const authState = {
    isLoading: true,
    isAuthenticated: false,
    token: null
  };
  
  export const authReducer = (state = authState, action) => {
    const newState = JSON.parse(JSON.stringify(state));
    switch (action.type) {
        case 'LOGIN_REQUEST': {
            return {
                isLoading: true, // Show a loading indicator.
                isAuthenticated: false
            }
        }
        case 'RESTORE_TOKEN': {
            return {
                isLoading: false, // Show a loading indicator.
                isAuthenticated: true,
                token: action.payload
            }
        }
        case 'LOGIN_FAILURE':
        return {
            isLoading: false,
            isAuthenticated: false,
            error: action.error
        }
        case 'LOGIN_SUCCESS':
        return {
            isLoading: false,
            isAuthenticated: true, // Dismiss the login view.
            token: action.payload
        }
        case 'LOGOUT': {
            return {
                isLoading: false, // Show a loading indicator.
                isAuthenticated: false,
                token: null
            }
        }
      default:
        return newState;
    }
  
    return newState;
  };

Auth.js

import AsyncStorage from '@react-native-async-storage/async-storage';
import { useDispatch } from 'react-redux';
import {loginRequest, loginSuccess, loginFailure, logOut} from '../redux/actions/authAction';

export const storeToken = async (value) => {
    try {
      await AsyncStorage.setItem('token', value)
    } catch (e) {
      // saving error
    }
  }

  export const getToken = async () => {
    try {
      const value = await AsyncStorage.getItem('token')
      if(value !== null) {
        return value
      } else{
          return null
      }
    } catch(e) {
      // error reading value
      console.log(e);
    }
  }

  export const removeToken = async () => {
    try {
      await AsyncStorage.removeItem('token')
    } catch(e) {
      // remove error
    }
    console.log('token removed.')
  }

  export const isLoggedIn = async () => {
    if(await getToken() != null){
        return true
    }
    return false
}

export const signOut = () => {
  removeToken()
}

export default {storeToken, getToken, removeToken, isLoggedIn, signOut }

LoginScreen.js

/* eslint-disable comma-dangle */
import React, { useEffect, useState, useCallback } from 'react';
import {
  View,
  TouchableHighlight,
  Text,
  TextInput,
  TouchableWithoutFeedback,
  Keyboard,
  ScrollView
} from 'react-native';
import {login} from '../../api/apiQueries'
import {storeToken} from '../../auth/auth'
import store from '../../redux/store';
import styles from './styles';

import { useDispatch, useSelector } from 'react-redux';
const authState = store.getState().auth;


const LogInScreen = ({route, navigation}) => {

  const [userName, setUserName] = useState("")
  const [password, setPassword] = useState("")

  const dispatch = useDispatch()

  onPressLogButton = () => {
    dispatch(login(userName, password))
  }

  return (
  <TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
    <ScrollView style={styles.container}>
      <View>
        <Text style={styles.title}>Sign in</Text>
        <View style={styles.inputContainer}>
          <TextInput
            style={styles.input}
            placeholder="User Name"
            onChangeText={text => setUserName(text)}
            value={userName}
          />
        </View>
        <View style={styles.inputContainer}>
          <TextInput
            style={styles.input}
            placeholder="Password"
            onChangeText={text => setPassword(text)}
            value={password}
          />
        </View>
        <View style={styles.logContainer}>
          <TouchableHighlight
            style={styles.loginContainer}
            onPress={() => onPressLogButton()}
          >
            <Text style={styles.logTxt}>Log in</Text>
          </TouchableHighlight>
          {/* <Text style={styles.orTxt}>OR</Text> */}
          {/* <TouchableHighlight
            style={styles.facebookContainer}
            onPress={() => this.onPressFacebookButton()}
          >
            <Text style={styles.facebookTxt}>Facebook Login</Text>
          </TouchableHighlight> */}
        </View>
      </View>
    </ScrollView>
  </TouchableWithoutFeedback>
);


}

export default LogInScreen
1 Answers

As discussed in the comments, the solution is to either make use of the useSelector hook, or to subscribe your component to store updates using the mapStateToProps parameter of the connect method. That way, it will run whenever the store updates through a dispatched action.

From the docs:

useSelector() will also subscribe to the Redux store, and run your selector whenever an action is dispatched. Link

This means, for your AppNavigation.js, for example, you can change the code to:

import { useSelector } from 'react-redux';

const Navigator = () => { 
    const authStat = useSelector((state) => state.auth);

    if(authStat.isLoading){
        return(
...

Reading from the store by direct access will do just that, but it does not imply a subscription for future changes.

Related