javascriptreactjsreact-routerreact-contextuse-reducer

React app page routing issue post successful user login


I have a react app where I use the useContext and useReducer hooks for the login and storage. While the login part works, what I want achieve is to redirect user to a specific page post successful login. I am using react-router@6 and tried to use useNavigate() to navigate user to particular route though it doesn't seem to work.

const AuthService = async (dispatch) => {
  
  const MSAL_CONFIG = {} // populate MSAL config for Microsoft Graph API for AD auth

  const msalInstance = new msal.PublicClientApplication(MSAL_CONFIG);

  try {
    const loginResponse = await msalInstance.loginPopup(scopes);
    var username = loginResponse.account.username;
    var userid = username.slice(0, username.indexOf("@"));

    const loginData = {
      auth_token: loginResponse.idToken,
      user: {
        name: loginResponse.account.name,
        id: userid,
        email: username,
      },
    };

    const sessionData = {
      user_id: userid,
      id_token: loginResponse.idToken,
      access_token: loginResponse.accessToken,
    }
 
    sessionStorage.setItem("currentUser", JSON.stringify(loginData));
    dispatch({ type: "LOGIN_SUCCESS", payload: loginData });
    
    return { loginData: loginData, error: null };
    // dispatch({ type: 'LOGIN_SUCCESS', payload: loginData });
    //sessionStorage.setItem('currentUser', JSON.stringify(data));
  } catch (err) {
    console.log("+++ Login error : ", err);
    dispatch({ type: "LOGIN_ERROR", error: err });
    return { loginData: null, error: err };
  }
};

In my header.jsx, I have below code to handle the login button. It makes a call to the above AuthService. The code post AuthService() call, i.e. the if block, doesn't take effect, so user never gets redirected to the dashboard page.

const handleLogin = async () => {
  await AuthService(dispatch) 
  console.log("userDetails.token : " + userDetails.token)
  if (Boolean(userDetails.token)) {
    navigate("/dashboard");
  } 
};

Solution

  • If I'm correct in understanding that this AuthService function eventually resolves and that the dispatched LOGIN_SUCCESS action updates the userDetails variable that is selected from the auth context state, then I think you have all that you need and are close to a working solution. The issue is that the userDetails value from the render cycle the handleLogin is called in is closed over in callback scope, it will never be a different value. If the userDetails.token value is falsey when handleLogin is called, it will remain falsey in the entire callback scope.

    The AuthService function appears to return the same loginData object that is passed in the dispatched LOGIN_SUCCESS action to the store. handleLogin should await this value and conditionally navigate.

    const AuthService = async (dispatch) => {
      ...
    
      try {
        const { account, idToken } = await msalInstance.loginPopup(scopes);
        const { name, username } = account;
        const userid = username.slice(0, username.indexOf("@"));
    
        const loginData = {
          auth_token: idToken,
          user: {
            name,
            id: userid,
            email: username,
          },
        };
    
        ...
        
        sessionStorage.setItem("currentUser", JSON.stringify(loginData));
        dispatch({ type: "LOGIN_SUCCESS", payload: loginData });
        
        return { loginData, error: null }; // <-- return value
      } catch (error) {
        dispatch({ type: "LOGIN_ERROR", error });
        return { loginData: null, error }; // <-- return value
      }
    };
    
    const handleLogin = async () => {
      const { loginData } = await AuthService(dispatch);
    
      if (loginData && loginData.auth_token) { // or loginData?.auth_token
        navigate("/dashboard", { replace: true });
      }
    };