TypeError: Network request failed randomly in IOS
Personne n'a encore pris cette issue.
- Langage dominant
- C++
- Étoiles
- 127k
- Forks
- 25.3k
- Merge moyen
- 1 j 23 h
- PR mergées (30 j)
- 4
Description
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
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Le rapport mentionne api.config, où fetchBaseQuery et l’intercepteur sont configurés, ainsi que profile.js, où trois requêtes RTK Query sont exécutées. Commencez par reproduire l’échec intermittent sur iOS tout en vérifiant si la requête atteint le serveur, puis comparez les appareils défaillants et ceux qui fonctionnent. Le travail est terminé lorsque la cause est isolée et qu’un correctif est vérifié.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- ios, javascript, react-native
- Domaine
- api, mobile, networking
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- Calme
- Clarté
- À clarifier
- Accessibilité débutants
- 35/100