4

I am writing an app using Ionic 5 with React and Redux. I am attempting to navigate to a new page, /tabs/home, upon a successful login attempt. I am trying to do this by pushing the new URL onto the react router history prop when I get a successful response from the backend. This is working in that it changes the url from /login to /tabs/home but the login page is still displayed.

index.tsx

import React from 'react';
import {render} from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { store } from './helpers/store';
import { Router } from 'react-router';
import CreateBrowserHistory from 'history/createBrowserHistory';

export const history = CreateBrowserHistory();

render(
    <Provider store={store}>
        <Router history={history}>
             <App />           
        </Router>
    </Provider>,
    document.getElementById('root')
);

App.tsx

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { history } from './index';
import { alertActions } from './actions/alert.actions';
import { Route } from 'react-router-dom';
import {
  IonApp,
} from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import { LoginPage } from './pages/Login';

import MainTabs from './pages/MainTabs';

function App() {
  const alert = useSelector(state => state.alert);
  const dispatch = useDispatch();

  useEffect(() => {
    history.listen((location, action) => {
      console.log(location);
      console.log(action);
      dispatch(alertActions.clear());
    });
  }, []);

  return (
    <IonApp>
        {/*
         // @ts-ignore*/}
        <IonReactRouter history={history}>
          <Route path="/tabs" component={MainTabs} />
          <Route path="/login" component={LoginPage} />
        </IonReactRouter>
    </IonApp>
  )
}

export default App;

Login.tsx

function LoginPage() {
  const [inputs, setInputs] = useState({
    username: '',
    password: ''
  });
  const [submitted, setSubmitted] = useState(false);
  const { username, password } = inputs;
  const loggingIn = useSelector(state => state.authentication.loggingIn);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(userActions.logout());
  }, []);

  function handleChange(e) {
    const { name, value } = e.target;
    setInputs(inputs => ({ ...inputs, [name]: value }));
  }

  function handleSubmit(e) {
     e.preventDefault();

     setSubmitted(true);
     if (username && password) {
       dispatch(userActions.login(username, password));
     }
  }

  return (
    <IonPage id="login-page">
      <IonHeader>
        <IonToolbar>
          <IonButtons slot="start">
            <IonMenuButton></IonMenuButton>
          </IonButtons>
          <IonTitle>Login</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>

        <form noValidate onSubmit={handleSubmit}>
          <IonList>
            <IonItem>
              <IonLabel position="stacked" color="primary">Username</IonLabel>
              <IonInput name="username" type="text" value={username} spellCheck={false} autocapitalize="off" onIonChange={handleChange} className={'form-control' + (submitted && !username ? ' is-invalid' : '')} required>
              </IonInput>
            </IonItem>

            <IonItem>
              <IonLabel position="stacked" color="primary">Password</IonLabel>
              <IonInput name="password" type="password" value={password} onIonChange={handleChange} className={'form-control' + (submitted && !password ? ' is-invalid' : '')} required>
              </IonInput>
            </IonItem>

          </IonList>

          <IonRow>
            <IonCol>
              {loggingIn && <span className="spinner-border spinner-border-sm mr-1"></span>}
              <IonButton type="submit" expand="block">Login</IonButton>
            </IonCol>
            <IonCol>
              <IonButton routerLink="/signup" color="light" expand="block">Signup</IonButton>
            </IonCol>
          </IonRow>
        </form>

      </IonContent>

    </IonPage>
  )
}

export { LoginPage };

login action

function login(username, password) {
    return dispatch => {
        dispatch(request({ username }));

        userService.login(username, password)
        .then(
            user => {
                dispatch(success(user));
                history.push('/tabs/home');
            },
            error => {
                dispatch(failure(error.toString()));
                dispatch(alertActions.error(error.toString()));
            }
        );
    };

    function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
    function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
    function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}
Vivek Doshi
  • 56,649
  • 12
  • 110
  • 122
Stalfurion
  • 81
  • 1
  • 13

3 Answers3

3

Just put the exact attribute in the Route to solve this

 <Route path="/tabs" exact component={MainTabs} />
 <Route path="/login" exact component={LoginPage} />
Thanveer Shah
  • 3,250
  • 2
  • 15
  • 31
  • Unfortunately this did not work for me, but thank you for the suggestion. It is still updating the URL but not rendering the new page. – Stalfurion Jun 17 '20 at 23:36
1

In your code you have

<Route path "/tabs" component={MainTabs} />

When instead it should be

<Route path "/tabs/home" component={MainTabs} />

When you're trying to push your history with /tabs/home there is no route specified for that, so it won't know where to go.

Also in your index file I would recommend having an additional route like so

<Route exact path "/tabs" component={SomeComponent} />
<Route path "/tabs/home" component={MainTabs} />

Just in case you plan on having more than one path attached to your /tabs route.

Michael
  • 1,454
  • 3
  • 19
  • 45
  • The route for /tabs/home is in the MainTabs component and there is a redirect there from /tabs to /tabs/home. I may be wrong on this but I think even if it was an issue with not being able to find the route it should still attempt to render a new page not just stay on the current page – Stalfurion Jun 19 '20 at 14:22
1

ISSUE :

As per the doc :

The IonReactRouter component wraps the traditional BrowserRouter component from React Router, and sets the app up for routing. Therefore, use IonReactRouter in place of BrowserRouter. You can pass in any props to IonReactRouter and they will be passed down to the underlying BrowserRouter.

I think custom history is not supported by BrowserRouter, as you can see BrowserRouter supports this props, there no history props there

basename
forceRefresh
getUserConfirmation
keyLength
children

history props is available for Router

Issue on github


SOLUTION :

So I've made below changes for using custom history and it's working


index.js

import React from "react";
import { render } from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./helpers/store";

render(
  <Provider store={store}>
      <App />
  </Provider>,
  document.getElementById("root")
);

App.tsx

    <IonApp>
      <Router history={history}>
        {/* <IonReactRouter history={history}> */}
        <Route path="/tabs" component={MainTabs} />
        <Route exact path="/" component={LoginPage} />
        {/* </IonReactRouter> */}
      </Router>
    </IonApp>

OR

You can use something like this as hack, ref

import React from 'react'
import { History } from 'history'
import { useHistory } from 'react-router'

// Add custom property 'appHistory' to the global window object
declare global {
  interface Window { appHistory: History }
}

const MyApp: React.FC = () => {
  // Store the history object globally so we can access it outside of React components
  window.appHistory = useHistory()

  ...
}
Vivek Doshi
  • 56,649
  • 12
  • 110
  • 122