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.