Quantcast
Viewing all articles
Browse latest Browse all 6398

Android App Session Expiry Error Popup shows up twice

Whenever the error type is ErrorType.SessionExpired, there are two errors displayed on Android in my app.I was able to fix it in iOS as you can see in my APIState/index.tsx file using isPopupVisible and the conditional code in the last 5 lines, but that code was causing Android to not show any error at all. So I added the if:

import React, { useContext, useEffect, useState } from 'react'import {  APIParams,  apiSelectors,  CallState,  ErrorType,  removeErrorCallStates,  removeValidationCallState,} from 'common/store/slices/api'import { LocalizationContext } from 'common/components/Translations'import { RootStateOrAny, useDispatch, useSelector } from 'react-redux'import LoaderModal from 'common/components/LoaderModal'import {  genericRequestHandling,  handleLogout,} from 'common/helperFunctions/serviceHelpers'import Alert, { AlertProps } from '../Alert'import { resetNavToDashboard } from 'core/RootNavigation'import { clearCoverage } from 'common/store/slices/benefit/benefit'import { authSelectors } from 'common/store/slices/auth/auth'import { Platform } from 'react-native'/** * The API State is responsible for displaying the error Popup * @returns void */const APIState = () => {  const [isPopupVisible, setPopupVisibility] = useState<boolean>(false)  const { translations } = useContext(LocalizationContext)  const dispatch = useDispatch()  const callStates = useSelector((state: RootStateOrAny) =>    apiSelectors.getCallStateMap(state),  )  const isLoggedIn = useSelector((state: RootStateOrAny) =>    authSelectors.isLoggedIn(state),  )  // if the value is loading, and we find it, and we do not exclude load, return true  const findCallStateInMap = (    value?: CallState,    map?: Record<string, APIParams>,  ) => {    if (map) {      return (Object.values(map) as Array<APIParams>).some(        (x) =>          ((x.callState === CallState.loading && !x.excludeLoad) ||            x.callState === CallState.error) &&          x.callState === value,      )    }    return false  }  const onPressTryAgainLater = () => {    setPopupVisibility(false)    dispatch(clearCoverage())    dispatch(removeErrorCallStates())    if (isLoggedIn) {      resetNavToDashboard()    }  }  const onPressRefresh = () => {    setPopupVisibility(false)    //refreshes all calls that did not complete successfully.    //since we often make calls on preceeding pages and continue before calls complete,    //this allows us to continue with our existing precedent, while still keeping track of all our calls    if (callStates)      (Object.values(callStates) as Array<APIParams>).forEach((call) => {        genericRequestHandling(          call.requestParams,          call.excludeLoad,          call.buttonsConfig,        )      })  }  const onPressOk = () => {    setPopupVisibility(false)    dispatch(removeValidationCallState())  }  const onPressExpired = () => {    setPopupVisibility(false)    setError(false)    handleLogout()  }  const findErrorDisplayType = (error: APIParams | undefined) => {    if (error) {      switch (error.errorType) {        case 'timeout of 0ms exceeded' || 'Network Error':          return ErrorType.NetworkError        case 401:          return ErrorType.SessionExpired        case 403:          return ErrorType.NotAuthorized        case 400:          return ErrorType.ValidationError        default:          return ErrorType.GeneralError      }    }    return undefined  }  const defaultErrorAlertSettings: AlertProps = {    alertTitle: translations['global.error'],    alertDescription: translations['global.error.api-text'],    btnTitleOne: translations['global.error.api-button'],    btnTitleTwo: translations['global.error.api-try-again-later'],    onPressBtnOne: onPressRefresh,    onPressBtnTwo: onPressTryAgainLater,  }  const networkErrorAlertSettings: AlertProps = {    alertTitle: translations['global.error'],    alertDescription: translations[`global.error.network.text`],    btnTitleOne: translations['global.error.api-button'],    btnTitleTwo: translations['global.error.api-try-again-later'],    onPressBtnOne: onPressRefresh,    onPressBtnTwo: onPressTryAgainLater,  }  const sessionExpiredAlertSettings: AlertProps = {    alertTitle: translations['global.session-expired'],    alertDescription: translations['global.session-expired.text'],    btnTitleOne: translations['global.session-expired.button'],    onPressBtnOne: onPressExpired,  }  const findErrorMessage = (    record: APIParams | undefined,    errorStatusCode = 400,  ): string => {    if (      record?.errorType === errorStatusCode &&      record?.errorTranslationKeys &&      record?.errorTranslationKeys.length > 0 &&      record?.errorTranslationKeys[0]    ) {      return `errors.${record?.errorTranslationKeys[0]}`    }    return 'global.error.api-text'  }  const setAlertSettings = (    alertSettings: AlertProps,    error?: APIParams | undefined,  ): AlertProps => {    if (error?.buttonsConfig?.hideTryAgain) {      delete alertSettings.onPressBtnTwo    }    return alertSettings  }  //since we can track many calls at a time,  //we need to prioritize which error display type to use for our error  const getError = (map?: Record<string, APIParams>) => {    if (map) {      const mapArr = Object.values(map) as Array<APIParams>      const error =        mapArr.find(          (x) =>            x.errorType === 'timeout of 0ms exceeded' ||            x.errorType === 'Network Error',        ) ||        mapArr.find((x) => x.errorType === 401) ||        mapArr.find((x) => x.errorType === 403) ||        mapArr.find((x) => x.errorType === 400) ||        mapArr.find((x) => x.errorType === 500) ||        mapArr[0]      return error    }    return undefined  }  //map the display settings to the unique error types  const mapErrorModalSettings = (    map?: Record<string, APIParams>,  ): AlertProps => {    const error = getError(map)    const errorDisplayType = findErrorDisplayType(error)    const errorMessage = findErrorMessage(error)    switch (errorDisplayType) {      case ErrorType.NetworkError:        return setAlertSettings(networkErrorAlertSettings, error)      case ErrorType.SessionExpired:        return setAlertSettings(sessionExpiredAlertSettings, error)      case ErrorType.NotAuthorized:        return setAlertSettings(defaultErrorAlertSettings, error)      case ErrorType.ValidationError:        return {          alertTitle: translations['global.error.validation.title'],          // @ts-ignore          alertDescription: translations[errorMessage],          btnTitleOne: translations['global.error.api-validation-ok-button'],          onPressBtnOne: onPressOk,        }      default: {        const errorMessageOverrideTranslationKey = findErrorMessage(error, 500)        const errorAlertSettings = {          ...defaultErrorAlertSettings,          alertDescription: translations[errorMessageOverrideTranslationKey],        }        return setAlertSettings(errorAlertSettings, error)      }    }  }  const [errorStateAlertProps, setErrorStateModalProps] = useState<AlertProps>(    defaultErrorAlertSettings,  )  const [loading, setLoading] = useState<boolean>(    findCallStateInMap(CallState.loading, callStates),  )  const [error, setError] = useState<boolean>(    findCallStateInMap(CallState.error, callStates) && !loading,  )  //update the loading and error state when callstates changes  useEffect(() => {    setLoading(findCallStateInMap(CallState.loading, callStates))    setError(findCallStateInMap(CallState.error, callStates) && !loading)  }, [callStates, loading])  // Login -> PopupVisibility = false  // Session Expiry Popup 1  //    -> Show alert && PopupVisibility = true, Accept popup  // Session Expiry Popup 2 -> Redirect to logout  // Login -> Good to go since error == null  useEffect(() => {    setErrorStateModalProps(mapErrorModalSettings(callStates))    // eslint-disable-next-line react-hooks/exhaustive-deps  }, [callStates, error])  //TODO: Fix issue that loader modal is overlapped by the keyboard when in this location  return (<>      {loading && <LoaderModal />}      {!loading &&        !isPopupVisible &&        Platform.OS === 'ios'&&        error && <Alert isVisible={true} {...errorStateAlertProps} /> &&        setPopupVisibility(true)}      {!loading &&        isPopupVisible &&        error &&        Platform.OS === 'ios'&&        handleLogout()}      {!loading && Platform.OS === 'android'&& error && (<Alert isVisible={true} {...errorStateAlertProps} />      )}</>  )}export default APIState

This is how the API State is used in the App.tsx

<ReduxProvider store={storeAndPersistor.store}><PersistGate loading={null} persistor={storeAndPersistor.persistor}><LocalizationProvider                localizedStrings={localizedStrings}                appLanguage={appLanguage}                refreshTranslation={() => setReload(true)}><GSCThemeProvider theme={theme}><StatusBar                    barStyle="dark-content"                    backgroundColor={theme.colors.secondaryLighter}                  /><APIState />                  {/* routes */}<AppStackScreen /></GSCThemeProvider></LocalizationProvider></PersistGate></ReduxProvider>

What can I do to fix this?
I have already tried to remove CallState as a dependency from UseEffect but that also ended up not showing any popup.


Viewing all articles
Browse latest Browse all 6398

Trending Articles