Search code examples
reactjsauthenticationionic-frameworkreact-router

Title: React history.push not redirecting on first click in a login function


I'm working on a React application using AWS Amplify for authentication and Ionic React for the UI. My issue is with the history.push method from react-router-dom, which does not seem to redirect the user after a successful login on the first click, but works on the second click. This behavior occurs in the LoginPage component where, after successful authentication with Amplify, I attempt to redirect the user to the homepage.

Here's a simplified version of my login function and useEffect hook in the LoginPage component:

import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { Auth } from 'aws-amplify';
import { IonButton, IonInput, IonPage } from '@ionic/react';

const LoginPage = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const history = useHistory();

  const login = async () => {
    try {
      const user = await Auth.signIn(email, password);
      console.log('Login successful:', user);
      history.push('/dashboard'); // Not redirecting on first click
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  return (
    <IonPage>
      <IonInput value={email} onIonChange={e => setEmail(e.detail.value)} />
      <IonInput type="password" value={password} onIonChange={e => setPassword(e.detail.value)} />
      <IonButton onClick={login}>Login</IonButton>
    </IonPage>
  );
};

export default LoginPage;

My homepage:

import { Route, Redirect } from 'react-router-dom'
import { IonRouterOutlet } from '@ionic/react'

import { IonLabel, IonIcon, IonTabs, IonTabBar, IonTabButton } from '@ionic/react'
import { wallet, flash, list, documentTextOutline } from 'ionicons/icons'

import Dashboard from './tabs/DashboardTab'
import PrivateRoute from './auth/PrivateRoute'
import LoginPage from './LoginPage'

const HomePage = ({ match }) => {
  return (
    <IonTabs>
      <IonRouterOutlet>
        <Route path="/mobile/login" component={LoginPage} exact={true} />
        <Route path="/mobile" render={() => <Redirect to="/mobile/dashboard" />} exact={true} />

        <PrivateRoute path="/mobile/dashboard" component={Dashboard} exact={true} />
      </IonRouterOutlet>

      <IonTabBar slot="bottom">
        <IonTabButton tab="tab1" href="/mobile/dashboard">
          <IonIcon icon={flash} />
          <IonLabel>Dashboard</IonLabel>
        </IonTabButton>
       
      </IonTabBar>
    </IonTabs>
  )
}

export default HomePage

PrivateRoute:

import React, { useState, useEffect } from 'react'
import { Route, Redirect } from 'react-router-dom'
import { Auth } from 'aws-amplify'
import { IonSpinner } from '@ionic/react'
import { useLocation } from 'react-router-dom'

const PrivateRoute = ({ component: Component, render, ...rest }) => {
  const [isLoggedIn, setIsLoggedIn] = useState(false)
  const [checkingStatus, setCheckingStatus] = useState(true)
  const [user, setUser] = useState(null)

  const location = useLocation()

  useEffect(() => {
    checkAuthState()
  }, [location])

  async function checkAuthState() {
    setCheckingStatus(true)
    try {
      const currentUser = await Auth.currentAuthenticatedUser()
      setIsLoggedIn(true)
      setUser(currentUser)
    } catch (error) {
      console.error('Not logged in', error)
      setIsLoggedIn(false)
    } finally {
      setCheckingStatus(false)
    }
  }
  return (
    <Route
      {...rest}
      render={(props) =>
        checkingStatus ? (
          <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
            <IonSpinner name="crescent" />
          </div>
        ) : isLoggedIn ? (
          render ? (
            render({ ...props, user })
          ) : (
            <Component {...props} user={user} />
          )
        ) : (
          <Redirect to="/mobile/login" />
        )
      }
    />
  )
}

export default PrivateRoute

Solution

  • After spending some time debugging, as suggested by @Drew Reese, I discovered that the redirect was actually working, but the page was not being rendered correctly. Or rather, there seemed to be an issue with how my routes were set up.

    While investigating my AppShell.js, I made a crucial modification to the routing configuration. Initially, I was using a regular to handle the redirection to the dashboard. However, I realized that this approach was not triggering the desired component rendering upon successful authentication.

    Here's the original snippet from my AppShell.js:

    <IonApp>
      <IonReactRouter>
        <IonRouterOutlet id="main">
          <Route path="/mobile" render={(props) => <HomePage {...props} />} />
          <Route path="/" render={() => <Redirect to="/mobile/dashboard" />} exact={true} />
        </IonRouterOutlet>
      </IonReactRouter>
    </IonApp>
    

    To resolve this, I changed the second to a . The intention behind this modification was to ensure that the redirection logic after login is encapsulated within a , ensuring that our authentication logic is consistently applied. Here is the revised configuration:

    <IonApp>
      <IonReactRouter>
        <IonRouterOutlet id="main">
          <Route path="/mobile" render={(props) => <HomePage {...props} />} />
          <PrivateRoute path="/" render={() => <Redirect to="/mobile/dashboard" />} exact={true} />
        </IonRouterOutlet>
      </IonReactRouter>
    </IonApp>
    

    This change was based on the understanding that a component, unlike a standard , incorporates authentication checks before rendering the specified component or redirecting the user. By applying this approach, I was able to ensure that users are redirected to the dashboard only after their authentication status is verified, addressing the issue where the dashboard was not being rendered properly due to a mishandling of routes in the context of authentication.

    Thanks!