Quantcast
Channel: Active questions tagged react-native+typescript - Stack Overflow
Viewing all articles
Browse latest Browse all 6287

Android App Session Expiry Error Popup shows up twice

$
0
0

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 6287

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>