TypeError: Network request failed randomly in IOS
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- C++
- Estrellas
- 127k
- Forks
- 25.3k
- Merge medio
- 1 d 23 h
- PR fusionados (30 d)
- 4
Descripción
Description
some users reported network request failed using rtk query or TypeError: Network request failed randomly for some api's and others works fine , according to this issue opened by me in redux toolkit link , the issue arises from react native environment , this issue happen randomly in ios , i mean same user in other device can request this api and works fine , the request not even reaches to our server.
api.config
import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react';
import Config from 'react-native-config';
import {I18nManager, Platform} from 'react-native';
import {getToken, getGuestPublicToken} from '@selectors';
import qs from 'qs';
import {getAppVersion} from '@utils/helpers';
const baseQuery = fetchBaseQuery({
baseUrl: `${Config.API_URL}`,
paramsSerializer: params => qs.stringify(params, {arrayFormat: 'brackets'}),
});
const baseQueryWithInterceptor = async (args, api, extraOptions) => {
const version = (await getAppVersion()).substring(1).split(' ')[0];
const state = api.getState();
const privateToken = getToken(state);
const publicToken = getGuestPublicToken(state);
args.headers = {
...args.headers,
'Accept-Language': I18nManager.isRTL ? 'ar' : 'en',
'User-Agent': Platform.OS,
'App-Version': version,
Authorization: `Bearer ${
privateToken ? privateToken?.access_token : publicToken?.access_token
}`,
};
let result = await baseQuery(args, api, extraOptions);
if (__DEV__) {
console.log(api.endpoint, args, result);
}
return {
...result,
error: result?.error?.data || result?.error,
};
};
export const api = createApi({
baseQuery: baseQueryWithInterceptor,
endpoints: () => ({}),
invalidationBehavior: 'immediately',
tagTypes: [
'Customer',
'order',
'Cards',
'ActiveCoupons',
'ExpiredCoupons',
'Wallet',
'WalletHistory',
'PromoCode',
'GetRunningOrder',
'Banners',
'ContactMessages',
],
});
profile.js
import {Animated, StyleSheet, View, Image, RefreshControl} from 'react-native';
import React, {memo, useCallback, useMemo, useRef, useState} from 'react';
import {useTranslation} from 'react-i18next';
import {useNavigation} from '@react-navigation/native';
import {useSelector} from 'react-redux';
import {getToken} from '@selectors';
import {
useGetCustomerQuery,
useGetQuestionnaireQuery,
useGetWalletQuery,
} from '@services';
import {
ProfileButton,
ProfileInfo,
QuestionnaireCard,
ReferCard,
Button,
EmptyView,
ErrorView,
LoadingView,
Header,
CheckMobileVerified,
} from '@components';
import {
crypto,
profile_heart,
wallet2,
edit,
profile2,
region,
coupon,
credit2,
tims_bag,
} from '@assets';
import {moderateScale, moderateVerticalScale} from 'react-native-size-matters';
import {colors, typo} from '@common';
import {TouchableOpacity} from 'react-native-gesture-handler';
import Modal from 'react-native-modal';
import {convertEnglishNumbersToArabic} from '@utils';
import {commonStyles} from '@utils/constants';
const EditProfileButton = memo(() => {
const {navigate} = useNavigation();
return (
<TouchableOpacity
onPress={() => navigate('EditProfile')}
style={styles.item}>
<Image style={styles.editIcon} source={edit} />
</TouchableOpacity>
);
});
const EditCountryButton = memo(() => {
const {navigate} = useNavigation();
return (
<TouchableOpacity onPress={() => navigate('Country')} style={styles.item}>
<Image style={styles.editIcon} source={region} tintColor={colors.white} />
</TouchableOpacity>
);
});
const Profile = () => {
const {t} = useTranslation();
const navigation = useNavigation();
const isLoggedIn = useSelector(getToken);
const [isModalVisible, setModalVisible] = useState(false);
const {
data,
isLoading: isLoadingQuestionnaire,
isError: isLoadingQuestionnaireError,
refetch: refetchQuestionnaire,
error: gettingQuestionnaireError,
} = useGetQuestionnaireQuery(undefined, {skip: !isLoggedIn});
const toggleModal = () => {
setModalVisible(!isModalVisible);
};
const {
currentData: user = {},
refetch: refetchCustomer,
isLoading: isGettingCustomer,
isFetching: isFetchingCustomer,
isError: isGettingCustomerError,
error: gettingCustomerError,
} = useGetCustomerQuery(undefined, {
skip: !isLoggedIn,
});
const {
data: {user_wallet} = {},
isLoading: isFetchingWallet,
isError: isFetchingWalletError,
refetch: refetchWallet,
error: gettingWalletError,
} = useGetWalletQuery(undefined, {
skip: !isLoggedIn,
});
const isLoading = useMemo(
() =>
[isLoadingQuestionnaire, isGettingCustomer, isFetchingWallet].some(
Boolean,
),
[isLoadingQuestionnaire, isGettingCustomer, isFetchingWallet],
);
const hasError = useMemo(
() =>
[
isLoadingQuestionnaireError,
isGettingCustomerError,
isFetchingWalletError,
].some(Boolean),
[
isLoadingQuestionnaireError,
isGettingCustomerError,
isFetchingWalletError,
],
);
const error = useMemo(
() =>
[
gettingQuestionnaireError,
gettingCustomerError,
gettingWalletError,
].find(Boolean),
[gettingQuestionnaireError, gettingCustomerError, gettingWalletError],
);
const scrollY = useRef(new Animated.Value(0)).current;
const isScrolling = useRef(false);
const handleScrollBeginDrag = () => {
isScrolling.current = true;
};
const navigationFromProfile = useCallback(
screenName => {
navigation.navigate(screenName);
},
[navigation],
);
const emptyViewActions = useMemo(
() => [
{
title: t('sign_in').toUpperCase(),
type: Button.types.PRIMARY,
onPress: () =>
navigation.navigate('AuthStack', {
screen: 'Login',
}),
},
],
[navigation],
);
const image = useMemo(
() => (user?.avatar ? {uri: user?.avatar} : profile2),
[user],
);
const onRetry = useCallback(
() =>
Promise.all([
isGettingCustomerError && refetchCustomer(),
isLoadingQuestionnaireError && refetchQuestionnaire(),
isFetchingWalletError && refetchWallet(),
]),
[
isGettingCustomerError,
isLoadingQuestionnaireError,
refetchCustomer,
refetchCustomer,
isFetchingWalletError,
refetchWallet,
],
);
const walletInfo = useMemo(
() =>
`${t('balance')} : ${convertEnglishNumbersToArabic(
Number(user_wallet?.total || 0).toFixed(2),
)} ${user_wallet?.currency}`,
[user_wallet, convertEnglishNumbersToArabic],
);
const opacity = scrollY.interpolate({
inputRange: [0, 50],
outputRange: [1, 0],
extrapolate: 'clamp',
});
const scale = scrollY.interpolate({
inputRange: [0, 50],
outputRange: [1, 0.8],
extrapolate: 'clamp',
});
const onRefresh = () => {
refetchCustomer();
};
return (
<>
<View>
<Header
rightItem={isLoggedIn ? EditProfileButton : EditCountryButton}
title={isLoggedIn ? null : t('profile')}
/>
</View>
{isLoggedIn ? (
<LoadingView isLoading={isLoading} containerStyle={styles.loadingView}>
<ErrorView hasError={hasError} onRetry={onRetry} error={error}>
<Animated.View
style={[
styles.profileImgContainer,
{opacity, transform: [{scale}]},
]}>
<View>
<TouchableOpacity
onPress={toggleModal}
style={styles.imageContainer}>
<View style={styles.profileImage}>
<Image source={image} style={styles.image} />
</View>
</TouchableOpacity>
</View>
</Animated.View>
<View style={[styles.mainContainer(user?.verified)]}>
<Animated.ScrollView
keyboardShouldPersistTaps="always"
refreshControl={
<RefreshControl refreshing={false} onRefresh={onRefresh} />
}
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: scrollY}}}],
{useNativeDriver: true},
)}
onScrollBeginDrag={handleScrollBeginDrag}
scrollEventThrottle={16}
bounces={false}
showsVerticalScrollIndicator={false}>
<View style={styles.container}>
<ProfileInfo />
<QuestionnaireCard />
<View style={styles.separator} />
<ReferCard />
<ProfileButton
icon={tims_bag}
label={t('my_orders')}
showBorderBottom={true}
onPress={() => navigationFromProfile('MyOrders')}
/>
<ProfileButton
icon={crypto}
label={t('points_analysis')}
showBorderBottom={true}
onPress={() => navigationFromProfile('PointsAnalysis')}
/>
<ProfileButton
icon={profile_heart}
label={t('favorites')}
showBorderBottom={true}
onPress={() => navigationFromProfile('Favorites')}
/>
<ProfileButton
icon={credit2}
label={t('my_cards')}
showBorderBottom={true}
onPress={() => navigationFromProfile('MyCards')}
/>
<ProfileButton
icon={wallet2}
label={t('wallet')}
label2={walletInfo}
showBorderBottom={true}
onPress={() => navigationFromProfile('Wallet')}
/>
<ProfileButton
icon={coupon}
label={t('Coupons')}
showBorderBottom={true}
onPress={() => navigationFromProfile('Coupons')}
/>
{/* <ProfileButton
icon={location}
label={t('my_addresses')}
showBorderBottom={true}
onPress={() => navigationFromProfile('MyAddresses')}
/> */}
<ProfileButton
icon={region}
label={t('country')}
icon2={user?.client_country?.country?.image_url}
onPress={() => navigationFromProfile('Country')}
/>
</View>
</Animated.ScrollView>
{!user?.verified && (
<CheckMobileVerified isMobileVerified={user?.verified} />
)}
<Modal isVisible={isModalVisible} onBackdropPress={toggleModal}>
<View style={styles.overlay}>
<View style={styles.modalContainer}>
<Image source={image} width="100%" height="100%" />
</View>
</View>
</Modal>
</View>
</ErrorView>
</LoadingView>
) : (
<View style={styles.fullHeight}>
<EmptyView
iconName={'account-alert-outline'}
iconColor={colors.gray}
title={t(
'sign_in_or_register_to_unlock_your_rewards_and_earn_points',
)}
actions={emptyViewActions}
titleStyle={styles.emptyViewTitle}
iconNameSize={moderateScale(100)}
actionsContainer={styles.actionsContainer}
/>
</View>
)}
</>
);
};
export default memo(Profile);
const styles = StyleSheet.create({
mainContainer: isVerified => ({
flex: 1,
backgroundColor: colors.white,
width: '100%',
paddingBottom: isVerified ? 0 : moderateVerticalScale(50),
}),
loadingView: {
flex: 1,
},
separator: {
height: moderateVerticalScale(11),
},
pleaseLoginText: {
fontSize: commonStyles.thirdFontSize,
color: colors.text,
marginBottom: moderateVerticalScale(20),
fontFamily: typo.sofiaBroRegular,
},
loginButton: {
width: moderateScale(150),
},
container: {
flex: 1,
paddingHorizontal: commonStyles.marginHorizontal,
backgroundColor: colors.white,
alignItems: 'center',
alignSelf: 'center',
width: '100%',
maxWidth: moderateScale(650),
marginBottom: commonStyles.marginVertical * 3,
},
pleaseLoginContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
gap: moderateVerticalScale(22),
paddingHorizontal: moderateScale(60),
},
pleaseLoginImageText1: {
fontSize: commonStyles.thirdFontSize,
color: colors.text,
fontFamily: typo.helvetica85Heavy,
marginBottom: moderateVerticalScale(11),
textAlign: 'center',
},
pleaseLoginImageText2: {
fontSize: commonStyles.thirdFontSize,
color: colors.text,
fontFamily: typo.sofiaBroRegular,
textAlign: 'center',
},
pleaseLoginImage: {
width: moderateScale(100),
height: moderateVerticalScale(100),
resizeMode: 'contain',
tintColor: colors.drift_wood,
},
pleaseLoginImageButton: {
width: '50%',
},
editIcon: {
height: moderateVerticalScale(30),
width: moderateScale(30),
resizeMode: 'contain',
tintColor: colors.white,
},
item: {
paddingHorizontal: commonStyles.marginHorizontal / 2,
},
addImage: {
width: moderateScale(27),
height: moderateVerticalScale(27),
position: 'absolute',
bottom: moderateVerticalScale(0),
right: moderateScale(5),
},
camera: {
width: moderateScale(35),
height: moderateVerticalScale(35),
resizeMode: 'contain',
},
image: {
width: moderateScale(90),
height: moderateVerticalScale(90),
borderRadius: moderateScale(45),
resizeMode: 'cover',
},
profileImgContainer: {
position: 'absolute',
zIndex: 99999999,
width: '50%',
flexDirection: 'row',
justifyContent: 'center',
top: '-6%',
left: '25%',
},
overlay: {
justifyContent: 'center',
alignItems: 'center',
},
modalContainer: {
width: moderateScale(300),
height: moderateVerticalScale(300),
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
backgroundColor: colors.black,
borderRadius: moderateScale(150),
},
fullHeight: {
flex: 1,
},
emptyViewTitle: {
fontFamily: typo.sofiaBroRegular,
fontSize: commonStyles.thirdFontSize,
},
actionsContainer: {position: 'absolute', bottom: '4%'},
profileImage: {
width: moderateScale(90),
height: moderateVerticalScale(90),
backgroundColor: colors.black,
borderRadius: moderateScale(45),
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
},
imageContainer: {
backgroundColor: colors.white,
borderRadius: moderateScale(150),
padding: commonStyles.marginHorizontal / 3,
},
});
Errorview.jsx
import React, {memo} from 'react';
import {useTranslation} from 'react-i18next';
import {StyleSheet, Text, View} from 'react-native';
import Button from './Button';
import {moderateVerticalScale} from 'react-native-size-matters/extend';
import {colors, typo} from '@common';
import {commonStyles} from '@utils/constants';
const ErrorView = ({children, hasError, onRetry, error}) => {
const {t} = useTranslation();
const generateErrorMsg = error => {
switch (error?.originalStatus || error?.status) {
case 'FETCH_ERROR':
return t('check_your_internet_connection');
case 502:
return t('we_are_updating_our_services_thanks_for_your_patience');
default:
return error?.error;
}
};
return hasError ? (
<View style={styles.container}>
<View style={styles.oopsContainer}>
<Text style={styles.oops}>{t('oops')}</Text>
<Text style={styles.wentWrong}>
{generateErrorMsg(error) || t('something_went_wrong')}
</Text>
</View>
<Button
title={t('try_again')}
onPress={onRetry}
style={styles.tryAgain}
/>
</View>
) : (
children
);
};
export default memo(ErrorView);
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.white,
paddingHorizontal: commonStyles.marginHorizontal,
},
someThingWentWrong: {
fontFamily: typo.sofiaBroBlack,
fontSize: commonStyles.thirdFontSize,
paddingBottom: moderateVerticalScale(25),
},
oopsContainer: {
paddingBottom: moderateVerticalScale(24),
gap: moderateVerticalScale(10),
},
oops: {
fontFamily: typo.sofiaBroBlack,
textAlign: 'center',
fontSize: commonStyles.semiBiggerFontSize,
color: colors.black,
},
wentWrong: {
fontFamily: typo.sofiaBroRegular,
textAlign: 'center',
fontSize: commonStyles.semiBiggerFontSize,
color: colors.black,
lineHeight: commonStyles.secondaryLingHeight,
},
tryAgain: {
backgroundColor: colors.main_red,
},
});
Steps to reproduce
- open app
- navigate to profile stack
- Use RTK Query to initiate a network request (e.g.,
useQuery) - Observe that the query returns a TypeError: Network request failed randomly randomly instead of executing successfully.
React Native Version
0.75.4
Affected Platforms
Runtime - iOS
Output of npx react-native info
System:
OS: macOS 12.7.6
CPU: (8) x64 Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
Memory: 53.96 MB / 16.00 GB
Shell:
version: 5.8.1
path: /bin/zsh
Binaries:
Node:
version: 20.15.0
path: ~/.nvm/versions/node/v20.15.0/bin/node
Yarn:
version: 4.5.0
path: ~/.nvm/versions/node/v20.15.0/bin/yarn
npm:
version: 10.7.0
path: ~/.nvm/versions/node/v20.15.0/bin/npm
Watchman:
version: 2024.07.15.00
path: /usr/local/bin/watchman
Managers:
CocoaPods:
version: 1.15.2
path: /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 21.4
- iOS 16.0
- macOS 12.3
- tvOS 16.0
- watchOS 9.0
Android SDK:
API Levels:
- "25"
- "28"
- "30"
- "31"
- "33"
- "34"
- "35"
Build Tools:
- 24.0.0
- 24.0.1
- 24.0.2
- 24.0.3
- 30.0.2
- 30.0.3
- 33.0.0
- 33.0.1
- 34.0.0
- 35.0.0
System Images:
- android-26 | Google Play Intel x86 Atom
- android-34 | Intel x86_64 Atom
- android-34 | Google APIs Intel x86_64 Atom
Android NDK: Not Found
IDEs:
Android Studio: 2024.1 AI-241.18034.62.2411.12169540
Xcode:
version: 14.0/14A309
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.11
path: /usr/bin/javac
Ruby:
version: 2.6.10
path: /usr/bin/ruby
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 18.3.1
wanted: 18.3.1
react-native:
installed: 0.75.4
wanted: 0.75.4
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: false
iOS:
hermesEnabled: true
newArchEnabled: false
Stacktrace or Logs
TypeError: Network request failed randomly
Reproducer
commercial app
Screenshots and Videos
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
El informe menciona api.config, donde se configuran fetchBaseQuery y el interceptor, y profile.js, donde se ejecutan tres solicitudes de RTK Query. Empieza por reproducir el fallo intermitente en iOS mientras compruebas si la solicitud llega al servidor; después, compara los dispositivos en los que falla con los dispositivos en los que funciona correctamente. Se considera terminado cuando la causa está aislada y se ha verificado una solución.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- ios, javascript, react-native
- Área
- api, mobile, networking
- Tipo de issue
- Error
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Estado de actividad
- Tranquilo
- Claridad
- Necesita aclaración
- Aptitud para principiantes
- 35/100