React Native Auth Flow with Redux (New Version) Token Fetch Problem

Viewed 157

So I have a mobile app in react native for which I am trying to create the auth flow properly.I had this first as a web app and then now trying to do the same as a mobile app. I am using redux for state management.

Here is the flow: Once the user logs in or registers, I send the data to the backend using axios. The backend generates a user token. I would like to create a system where I can fetch the token when a user logs in and store that with AsyncStorage so that I can directly log the user in when he or she tries to open the app again.

Here is the code, more than happy to answer any questions, this was done through a react tutorial really:

LoginScreen.js

const Login = ( { navigation }) => {

  const [email, setEmail] = useState();
  const [password, setPassword] = useState();
  const [message, setMessage] = useState(null)

  // Errors
  const [EmailMessage, setEmailMessage] = useState(null);
  const [PasswordMessage, setPasswordMessage] = useState(null);

  const dispatch = useDispatch();

  const userLogin = useSelector((state) => state.userLogin);
  const { loading, error, userInfo } = userLogin

  useEffect(() => {

      if (userInfo) {
          navigation.navigate('MainTabs', { screen: 'Home'});
      }
  }, [navigation, userInfo]);

  const submitHandler = (e) => {  
    
    e.preventDefault();
    
    if (!email) {
      alert('Please fill in the email');
      return
    };
    if (!password) {
      alert('Please fill in the password');
      return
    }
       
    dispatch(login(email, password));

    };

The return part is the frontend code so left that out for the sake of brevity

Now the redux part:

userActions.js

import axios from "axios";
import { USER_LOGIN_FAIL, USER_LOGIN_REQUEST, USER_LOGIN_SUCCESS, USER_LOGOUT, USER_REGISTER_FAIL, USER_REGISTER_SUCCESS, USER_REGISTER_REQUEST } from "../constants/userConstants"
import AsyncStorage from '@react-native-async-storage/async-storage';



export const login = (email, password) => async(dispatch) => {
    try {
        dispatch({ type: USER_LOGIN_REQUEST });

        const config = {
            headers: {
                "Content-type": "application/json"
            }
        }

        const {data} = await axios.post("api/url", {email, password}, config)
        dispatch({type: USER_LOGIN_SUCCESS, payload:data});
        
        await AsyncStorage.setItem("userInfo", JSON.stringify(data))
        
    } catch (error) {
        dispatch({
            type: USER_LOGIN_FAIL,
            payload: 
                error.response && error.response.data.message
                    ? error.response.data.message 
                    : error.message,
        })

        console.log("This login attempt is unsuccessful");
        
    }
}

export const logout = () => async (dispatch) => {
    await AsyncStorage.removeItem("userInfo")
    dispatch({ type: USER_LOGOUT });
};

export const register = (full_name, email, password, social) => async(dispatch) => {
    try {

        dispatch({type: USER_REGISTER_REQUEST});

        const config = {
            headers: {
                "Content-type": "application/json"
            }
        };

        const {data} = await axios.post("api/url", {full_name, email, password, social}, config);

        dispatch({ type: USER_REGISTER_SUCCESS, payload: data});
        dispatch({ type: USER_LOGIN_SUCCESS, payload: data});

        await AsyncStorage.setItem("userInfo", JSON.stringify(data))

    } catch (error) {
        dispatch({type: USER_REGISTER_FAIL, payload:
            error.response && error.response.data.message
                ? error.response.data.message
                : error.message      
    })
}}

userReducers.js

import { USER_LOGIN_REQUEST, USER_REGISTER_FAIL, USER_REGISTER_REQUEST, USER_REGISTER_SUCCESS } from "../constants/userConstants";
import { USER_LOGIN_SUCCESS, USER_LOGIN_FAIL, USER_LOGOUT   } from "../constants/userConstants";

export const userLoginReducer = (state = {}, action) => {
    switch (action.type) {
        case USER_LOGIN_REQUEST:
            return {loading: true}
        case USER_LOGIN_SUCCESS:
            return {loading: false, userInfo: action.payload}
        case USER_LOGIN_FAIL:
            return {loading: false, error:action.payload}
        case USER_LOGOUT:
            return {}

   
        default:
            return state;
    }
}

export const userRegisterReducer = (state = {}, action) => {
    switch (action.type) {
        case USER_REGISTER_REQUEST:
            return {loading:true}    
        case USER_REGISTER_SUCCESS:
            return {loading:false, userInfo: action.payload}
        case USER_REGISTER_FAIL:
            return {loading:false, error: action.payload}
            
        default:
            return state;
    }
}

store.js

import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
import { userLoginReducer, userRegisterReducer } from './src/redux/reducers/userReducers';
import AsyncStorage from '@react-native-async-storage/async-storage';

const reducer = combineReducers({
    //contains reducers
    userLogin: userLoginReducer,
    userRegister: userRegisterReducer
});

const middleware = [thunk];


const getData = async () => {
    try {
        const value = await AsyncStorage.getItem("userInfo")
        return value != null ? JSON.parse(value) : null;
        
    } catch(e) {
        console.log("this attempt is not successful");
    }
}

const userInfoFromStorage = getData() || null;
alert(JSON.stringify(userInfoFromStorage))

const initialState = {
    userLogin: {userInfo: userInfoFromStorage}
};


const store = createStore(
    reducer,
    initialState,
    applyMiddleware(...middleware)
);

export default store

I would appreciate any help here as I am not able to resolve how to solve this is this is a promise issue on fetch or something more general than that. I have a token generated in the backend once I register a user. I would really appreciate if anyone knows of the best way on how to fetch and save that token and log the user in if the user had already logged in

1 Answers

First, create a file with the name authProvider.ts. This will hold objects, with key as isAuthenticated and async getAuthToken() with callback function as property.

const authProvider: any = {
  isAuthenticated: false,
  authToken: null,
  async getAuthToken(callback: VoidFunction) {
    await GetSessionData('Access-token').then(
      res => {
        if (res) {
          authProvider.isAuthenticated = true;
          authProvider.authToken = res;
          callback();
        } else {
          authProvider.isAuthenticated = false;
          authProvider.authToken = res;
          callback();
        }
      }
    ); 
  }
};

export { authProvider };

export const GetSessionData =  async (key: string) => {
  return await AsyncStorage.getItem(
    key,
    (err, value) => {
      if (err) {
          console.log(err);
      } else {
        return value;
      }
    }
  );
}

In app.tsx In App component we will call the above authProvider to get the token from Async storage using await. Once done it will execute the below callback function logic.

const App = () => {
const [isLoading, setLoading] = useState<boolean>(true);
useEffect(() => {
 authProvider.getAuthToken(() => {
  if (authProvider.isAuthenticated) {
   store.dispatch(isAuthenticatedUser(true));
   setLoading(false);
  } else {
    store.dispatch(isAuthenticatedUser(false));
    setLoading(false);
    // *** redirect to login page logic ***.
  }
 });
}, []);
return (
  <>
     <Provider store={store}>
       {
         !isLoading ?
         <Dashboard /> :
         <AppLoader />
       }
     </Provider>
   </>
 );
}
Related