Error: this.props.history is undefined

Error: TypeError: undefined is not an object (evaluating ‘this.props.history.push’)

I am probably not passing history correctly!?

_
My code:

Login.tsx

import {
    IonPage,
    IonContent,
    IonList,
    IonItem,
    IonLabel,
    IonInput,
    IonButton,
    IonImg,
    IonAlert,
    IonSpinner
} from '@ionic/react';
import React from 'react';
import { RouteComponentProps } from 'react-router';
import { InputChangeEventDetail } from '@ionic/core';
import fetch, {
    FetchData,
    FetchError,
    InvalidResponseError
} from '../ormon';
import { store } from '../utils/Store';

type LoginState = {
    isLoading: boolean,
    error: FetchError | null,
    username: string,
    password: string,
    twoFACode: string,
    twoFANeedsSMSCode: boolean,
    twoFANeedsOTPCode: boolean
};

import { useHistory } from 'react-router';

export default class Login extends React.PureComponent<RouteComponentProps, LoginState> {

    

  constructor(props: RouteComponentProps) {
      super(props);

      this.state = {
          isLoading: false,
          error: null,
          username: "",
          password: "",
          twoFACode: "",
          twoFANeedsSMSCode: false,
          twoFANeedsOTPCode: false
      };
  }

  updateUsername = (event: CustomEvent<InputChangeEventDetail>) => {
      this.setState({ username: event.detail.value ? event.detail.value.trim() : "" });
  }

  updatePassword = (event: CustomEvent<InputChangeEventDetail>) => {
    this.setState({ password: event.detail.value ? event.detail.value.trim() : "" });
  }

  updateTwoFACode = (event: CustomEvent<InputChangeEventDetail>) => {
    this.setState({ twoFACode: event.detail.value ? event.detail.value.trim() : "" });
  }

  componentDidMount() {
    console.log('componentDidMount');

    store.clear();
  }

  render () {
    return (
        <IonPage>
            <IonContent className="ion-padding ion-justify-content-end"
                scrollX={ false }
                scrollY={ false }
                style={{
                    maxWidth: '480px',
                    alignSelf: 'center'
                }}>
                
                <form style={{
                    position: 'relative',
                    top: '50%',
                    transform: 'translateY(-50%)'
                }} onSubmit={ this.doLogin }>
                    <IonList>
                        { !this.state.twoFANeedsSMSCode && ! this.state.twoFANeedsOTPCode &&
                            <div>
                                <IonItem>
                                    {/*<IonLabel position="floating">Benutzername</IonLabel>*/}
                                    <IonInput label="Benutzername" name="username"
                                        required
                                        clearInput
                                        autofocus
                                        autocapitalize="characters"
                                        value={ this.state.username }
                                        onIonChange={ this.updateUsername } />
                                </IonItem>
                                <IonItem>
                                    {/*<IonLabel position="floating">Passwort</IonLabel>*/}
                                    <IonInput label="Passwort" name="password"
                                        type="password"
                                        required
                                        clearInput
                                        clearOnEdit
                                        value={ this.state.password }
                                        onIonChange={ this.updatePassword } />
                                </IonItem>
                            </div>
                        }
                        { this.state.twoFANeedsSMSCode &&
                            <IonItem>
                                <IonLabel position="floating">SMS Code</IonLabel>
                                <IonInput name="sms"
                                    placeholder="abcd.efgh"
                                    autofocus
                                    required
                                    value={ this.state.twoFACode }
                                    onIonChange={ this.updateTwoFACode } />
                            </IonItem>
                        }
                        { this.state.twoFANeedsOTPCode &&
                            <IonItem>
                                <IonLabel position="floating">Authenticator App Code</IonLabel> 
                                <IonInput name="otp"
                                    placeholder="XXX XXX"
                                    autofocus
                                    required
                                    value={ this.state.twoFACode }
                                    onIonChange={ this.updateTwoFACode } />
                            </IonItem>
                        }
                    </IonList>

                    { !this.state.twoFANeedsSMSCode && !this.state.twoFANeedsOTPCode &&
                        <IonButton expand="block" type="submit" style={{ marginTop: '15px' }}>Login</IonButton>
                    }
                    
                    { (this.state.twoFANeedsSMSCode || this.state.twoFANeedsOTPCode) &&
                        <div>
                            <IonButton expand="block" color="medium" onClick={ this.doReset } style={{ marginTop: '15px', float: 'left' }}>Zurück</IonButton>
                            <IonButton expand="block" type="submit" style={{ marginTop: '15px', float: 'right' }}>Weiter</IonButton>
                        </div>                    
                    }
                </form>
            </IonContent>

            { this.state.error && <IonAlert isOpen={ true }
                header={ this.state.error.title ? this.state.error.title : this.state.error.name }
                message={ this.state.error.message }
                buttons={[{ text: "Ok" }]}
                onDidDismiss={ () => { this.setState({ error: null }); } } />
            }

            { this.state.isLoading && <div style={{
              display: 'flex',
              justifyContent: 'center',
              alignItems: 'center',
              position: 'absolute',
              top: 0, right: 0, bottom: 0, left: 0,
              zIndex: 100,
              backgroundColor: 'white',
              opacity: 0.5
            }}><IonSpinner color="dark" /></div> }
        </IonPage>
    );
  }

	doReset = (event: any) => {
		console.log('doReset');

		store.clear();

		this.setState({
			isLoading: false,
			error: null,
			username: "",
			password: "",
			twoFACode: "",
			twoFANeedsSMSCode: false,
			twoFANeedsOTPCode: false
		});
	};

	doLogin = (event: React.FormEvent<HTMLFormElement>) => {
		console.log('doLogin');

		event.preventDefault();
		this.setState({ isLoading: true });

		if(store.userSessionKey && !this.state.twoFANeedsSMSCode && !this.state.twoFANeedsOTPCode){
			console.log('store.clear();');

			store.clear();
		}

		if(!store.userSessionKey){
			console.log('!store.userSessionKey');

			fetch(store.apiURL, {
				trtyp: "lon004",
				uid: this.state.username,
				pw1: this.state.password,
				slicd: "",
				remad: store.deviceIdentifier,
				trnsuid: this.state.username,
				trnspw1: this.state.password
			
			}).then(response => {
				console.log('then 4');

				const data = response.body as FetchData;
				if( !data || !data.seskey ){
					throw new InvalidResponseError("Session key is missing from response.");
				}
				store.userSessionKey = data.seskey;

				if (data.indchp !== '0') {
					console.log('data.indchp !== 0');
					/** The user needs to change password. This can't be done in the mobile app for now,
					* so we simply throw an exception and abort the login process: */
					throw new InvalidResponseError("Password expired: You need to set a new password by logging in from your desktop.");
				
				} else if (data.indsms === '1') {
					console.log('data.indsms === 1');
					// 2FA: Method possibly changed from OTP to SMS, so we require the OTP Code next time:
					store.twoFASkipOTPCode = false;

					// 2FA: SMS is enabled
					this.setState({ isLoading: false, twoFANeedsSMSCode: true });

				} else if (data.indqrc === '1' && !store.twoFASkipOTPCode) {
					console.log('data.indqrc === 1 && !store.twoFASkipOTPCode');
					// 2FA: OTP (One-Time-Password) is enabled
					this.setState({ isLoading: false, twoFANeedsOTPCode: true });

				} else {
					console.log('ret this.loadData()');
					return this.loadData();
				}

			}).catch(error => {
				console.log('catch err');

				this.setState({
					isLoading: false,
					error: error
				});
			});

		} else if(this.state.twoFANeedsSMSCode || this.state.twoFANeedsOTPCode) {
			console.log('this.state.twoFANeedsSMSCode || this.state.twoFANeedsOTPCode');

			if(!this.state.twoFACode || !this.state.twoFACode.trim()) {
				this.setState({
					isLoading: false,
					error: new FetchError("Please enter the " + (this.state.twoFANeedsSMSCode ? 'SMS' : 'authenticator app') + " code.")
				});
				return;
			}

			fetch(store.apiURL, {
				trtyp: "lon005",
				seskey: store.userSessionKey,
				remad: store.deviceIdentifier,
				code3: this.state.twoFACode.trim()

			}).then(() => {
				console.log('then case 3');

				// 2FA: Only ask for OTP Code for the very first time
				if(this.state.twoFANeedsOTPCode) {
					console.log('then case 3 store.twoFASkipOTPCode = true');
					store.twoFASkipOTPCode = true;
				}
				return this.loadData();

			}).catch(error => {
				this.setState({
					isLoading: false,
					error: error
				});
			});
		}
	};

	loadData = async () => {
		console.log('ƒ loadData');

		return fetch(store.apiURL, {
			trtyp: "adm1034",
			trtypalt: "adm1034m",
			seskey: store.userSessionKey,
			indspc1: 1,
			indsuts: 1,
			usrorg: 0,
			remad: store.deviceIdentifier,
			pos: 0,
			nrc: 9999,
			kndsb: '00'

		}).then(response => {
			console.log('then case 1');

			const data = response.body as FetchData;
			store.orgCCY = data.orgCCY;
			
			// check if single client
			return fetch(store.apiURL, {
				trtyp: "adm1034",
				trtypalt: "adm1034z",
				seskey: store.userSessionKey,
				remad: store.deviceIdentifier,
				pos: 0,
				nrc: 9999
			})

		}).then(response => {
			console.log('then case 2');

			const data = response.body as FetchData[];
			if( !data ){
				throw new InvalidResponseError("Data is not an array.");
			}
			

			console.log('checking this', this);
			console.log('checking this.props', this.props);

			if(data.length > 1){
				store.isSingleLogin = false;
				console.log('isSingleLogin false');
				this.props.history.push('/main/client-list');
			} else {
				store.isSingleLogin = true;
				console.log('isSingleLogin true');
				this.props.history.push('/main');
			}

			this.setState({
				isLoading: false,
				username: "",
				password: "",
				twoFACode: "",
				twoFANeedsSMSCode: false,
				twoFANeedsOTPCode: false
			});
		});
	};
}

App.tsx

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

import Main from './pages/Main';
import Login from './pages/Login';
import Cash from './pages/Cash';
import CashDetail from './pages/CashDetail';
import Securities from './pages/Securities';
import SecurityDetail from './pages/SecurityDetail';
import SecurityAmountDetail from './pages/SecurityAmountDetail';
import ClientList from './pages/ClientList';
import Totals from './pages/Totals';
import ReportPerformanceAdHoc from './pages/ReportPerformanceAdHoc';
import { store } from './utils/Store';

import '@ionic/core/css/core.css';
import '@ionic/core/css/ionic.bundle.css';

/* Theme variables */
import './theme/variables.css';

/* Internationalization Support */
import { IntlProvider } from 'react-intl';

import { IonReactRouter } from '@ionic/react-router';

export default class App extends React.PureComponent<{},{}> {

  render (){
    return (
    <IntlProvider locale="de-CH">
        <IonApp>
          <IonReactRouter>
            <IonRouterOutlet>
              <Route exact path="/login" component={ Login } />
              <Route exact path="/" render={() => {
                if( store.userIsLoggedIn ){
                  return (<Redirect to="/main" />);
                }
                return (<Redirect to="/login" />);
              }} />

              <Route exact path="/main" component={ Main } />
              <Route exact path="/main/client-list" component={ ClientList } />
              <Route exact path="/main/cash" component={ Cash } />
              <Route exact path="/main/cashdetail" component={ CashDetail } />
              <Route exact path="/main/securities" component={ Securities } />
              <Route exact path="/main/securitydetail" component={ SecurityDetail } />
              <Route exact path="/main/securityamountdetail" component={ SecurityAmountDetail } />
              <Route exact path="/main/totals" component={ Totals } />
              <Route exact path="/main/report/performance-adhoc" component={ ReportPerformanceAdHoc } />
            </IonRouterOutlet>
          </IonReactRouter>
        </IonApp>
      </IntlProvider>
    );
  }
}

package.json

{
  "name": "myApp",
  "private": true,
  "version": "0.0.1",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "test.e2e": "cypress run",
    "test.unit": "vitest",
    "lint": "eslint"
  },
  "dependencies": {
    "@amcharts/amcharts4": "^4.10.22",
    "@capacitor/app": "5.0.7",
    "@capacitor/core": "5.7.3",
    "@capacitor/haptics": "5.0.7",
    "@capacitor/keyboard": "5.0.8",
    "@capacitor/status-bar": "5.0.7",
    "@ionic/react": "^7.0.0",
    "@ionic/react-router": "^7.0.0",
    "@types/react-router": "^5.1.20",
    "@types/react-router-dom": "^5.3.3",
    "ionicons": "^7.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router": "^5.3.4",
    "react-router-dom": "^5.3.4",
    "react-intl": "^6.2.2"
  },
  "devDependencies": {
    "@capacitor/cli": "5.7.3",
    "@testing-library/dom": ">=7.21.4",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^14.0.0",
    "@testing-library/user-event": "^14.4.3",
    "@types/react": "^18.0.27",
    "@types/react-dom": "^18.0.10",
    "@vitejs/plugin-legacy": "^5.0.0",
    "@vitejs/plugin-react": "^4.0.1",
    "cypress": "^13.5.0",
    "eslint": "^8.35.0",
    "eslint-plugin-react": "^7.32.2",
    "jsdom": "^22.1.0",
    "terser": "^5.4.0",
    "typescript": "^5.1.6",
    "vite": "^5.0.0",
    "vitest": "^0.34.6"
  },
  "description": "An Ionic project"
}
1 Like

What is the output in the console of this.props?

It’s an empty object.

(Of course it did not used to be when I built the app before upgrading to Ionic 7)

Screenshot 2024-03-25 at 03.44.56