I’m encountering an issue where my React Native components are working fine on Android but not on iOS. I’m using React Native without expo, pods are well installed and the app build normally in Xcode. Thank you very much for your help!
The errors message I’m getting on iOS are:
Steps Taken:
-
Verified Imports and Exports: Ensured that all components are correctly imported and exported.
-
Console Logs: Added console logs to verify that the components are being imported correctly. The logs show
[Function CustomText]
and[Function CustomButton]
, indicating that the components are being imported. -
Clean and Rebuild: Cleaned the project and rebuilt it using
npx react-native run-ios
. -
Reinstall Dependencies: I reinstalled node_modules, and Pods.
-
Checked Permissions: Ensured that all necessary permissions are correctly set up for iOS.
-
Checked for Circular Dependencies: Ensured there are no circular dependencies in the imports.
-
Restarted Metro Bundler: Restarted the Metro bundler to pick up changes.
Code Snippets:
- ProfileScreen.jsx:
import AsyncStorage from '@react-native-async-storage/async-storage'; import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native'; import { View, Text, Modal, TouchableOpacity, StyleSheet, Image, Dimensions, TextInput, Button, ScrollView, Alert } from 'react-native'; import { GoogleSignin } from '@react-native-google-signin/google-signin'; import { CustomButton, CustomText } from '../components/CustomComponents'; import { faEye, faTimes } from '@fortawesome/free-solid-svg-icons'; import sponsorshipAPI from '../services/sponsorshipAPI'; import userAPI from '../services/userAPI'; import roleAPI from '../services/roleAPI'; import { useCallback, useEffect, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'; import { faChevronRight } from '@fortawesome/free-solid-svg-icons'; import { useAuth } from '../contexts/AuthContext'; import { TabBar, TabView } from 'react-native-tab-view'; import { BadgPoint, Card4, Card42 } from '../components/formationsCard'; import reviewAPI from '../services/reviewAPI'; import trainingAPI from '../services/trainingAPI'; import { launchCamera, launchImageLibrary } from 'react-native-image-picker'; import { request, PERMISSIONS, RESULTS } from 'react-native-permissions'; const screenHeight = Dimensions.get('window').height; const screenWidth = Dimensions.get('window').width; const statusStyles = { pending: { backgroundColor: '#FFDDC1', color: '#FF7F50' }, processing: { backgroundColor: '#FFFFCC', color: '#FFD700' }, approved: { backgroundColor: '#C6F6D5', color: '#05CD99' }, denied: { backgroundColor: '#FED7D7', color: '#FC8181' }, validated: { backgroundColor: '#BEE3F8', color: '#3182CE' }, default: { backgroundColor: '#E2E8F0', color: '#A0AEC0' } }; const getStatusStyle = (status) => { switch (status.toLowerCase()) { case 'pending': return statusStyles.pending; case 'processing': return statusStyles.processing; case 'approved': return statusStyles.approved; case 'denied': return statusStyles.denied; case 'validated': return statusStyles.validated; default: return statusStyles.default; } }; const translateStatus = (status) => { switch (status.toLowerCase()) { case 'pending': return 'En attente'; case 'processing': return 'En cours'; case 'approved': return 'Approuvée'; case 'denied': return 'Refusée'; case 'validated': return 'Validé'; default: return 'Statut inconnu'; } }; const TabContent1 = ({ userId }) => { const [reviews, setReviews] = useState([]); const [selectedImage, setSelectedImage] = useState(null); const [imageCount, setImageCount] = useState(0); const [pseudos, setPseudos] = useState({}); const navigation = useNavigation(); const fetchPseudo = async (userId) => { try { const result = await userAPI.getPseudoFromUserId(userId); if (result.result) { return result.pseudo; } else { console.error("Failed to fetch user pseudo:", result.error); return ''; } } catch (error) { console.error("Error fetching user pseudo:", error); return ''; } }; const goToCourse = async (trainingId) => { try { const trainingDetails = await trainingAPI.getTrainingById(trainingId); navigation.navigate('CoursPres', { training: trainingDetails }); } catch (error) { console.error("Failed to fetch training details:", error); } }; useFocusEffect( useCallback(() => { const fetchUserReviews = async () => { if (userId) { const result = await reviewAPI.getAllReviewsByUser(userId); const reviewsWithImages = result.filter(review => review.image_uri); setReviews(reviewsWithImages); setImageCount(reviewsWithImages.length); // Set the count of reviews with images const pseudoPromises = reviewsWithImages.map(async review => { if (review?.training_id?.user_id?._id) { const pseudo = await fetchPseudo(review.training_id.user_id._id); return { reviewId: review._id, pseudo }; } return { reviewId: review._id, pseudo: '' }; }); const pseudoResults = await Promise.all(pseudoPromises); const pseudoMap = pseudoResults.reduce((acc, { reviewId, pseudo }) => { acc[reviewId] = pseudo; return acc; }, {}); setPseudos(pseudoMap); } }; fetchUserReviews(); }, [userId]) ); const openImage = (uri) => { setSelectedImage(uri); }; const closeImage = () => { setSelectedImage(null); }; const goToReview = () => { navigation.navigate('AddReview'); }; return ( <ScrollView contentContainerStyle={styles.realisations}> <View style={styles.realHeader}> <View> <CustomText style={{ marginTop: '5%' }}>{imageCount} réalisations</CustomText> </View> <View style={{ flexDirection: 'row', gap: 15 }}> <TouchableOpacity> <Image source={require('../assets/filters.png')} style={{ width: 30, height: 30, marginTop: '10%' }} /> </TouchableOpacity> <TouchableOpacity onPress={goToReview}> <Image source={require('../assets/plusbt.png')} style={{ width: 35, height: 35 }} /> </TouchableOpacity> </View> </View> <View style={styles.reviewsContainer}> {reviews.map(review => { const statusStyle = getStatusStyle(review.status); const translatedStatus = translateStatus(review.status); const pseudo = pseudos[review._id] || ''; return ( <View key={review._id} style={styles.imageWrapper}> {review.status && ( <View style={{ borderRadius: 5, backgroundColor: statusStyle.backgroundColor, zIndex: 999, padding: '3%', width: '40%', left: 85, top: 30 }}> <CustomText style={{ fontSize: 8, color: statusStyle.color, fontFamily: 'Poppins-Regular', }}> {translatedStatus} </CustomText> </View> )} <Image source={{ uri: review.image_uri }} style={styles.nailImage} /> <BadgPoint taille={0.27} nbrPoints={review?.training_id?.difficulty?.point || 0} position={'absolute'} top={30} left={5} /> <TouchableOpacity style={styles.eyeIcon} onPress={() => openImage(review.image_uri)}> <FontAwesomeIcon icon={faEye} size={20} color="#fff" /> </TouchableOpacity> <View style={{ marginTop: -20 }}> <CustomText style={styles.caption}>{review?.training_id?.title || 'Titre non disponible'}</CustomText> <CustomText style={styles.pseudo}>{pseudo}</CustomText> <TouchableOpacity style={{ flexDirection: 'row', gap: 10, marginTop: '5%' }} onPress={() => goToCourse(review.training_id._id)}> <CustomText style={{ fontSize: 10, fontFamily: 'Poppins-SemiBold' }}>Voir la formation</CustomText> <FontAwesomeIcon icon={faChevronRight} size={15} color="black" /> </TouchableOpacity> </View> </View> ); })} </View> {selectedImage && ( <Modal visible={true} transparent={true}> <View style={styles.fullScreenContainer}> <Image source={{ uri: selectedImage }} style={styles.fullScreenImage} /> <TouchableOpacity style={styles.closeIcon} onPress={closeImage}> <FontAwesomeIcon icon={faTimes} size={30} color="#fff" /> </TouchableOpacity> </View> </Modal> )} </ScrollView> ); }; const TabContent2 = () => { const onPressItemCard4 = async (index) => { try { console.log( index + " voir cette formatrice"); } catch (error) { console.log(error) } } const onPressFormatrice = async () => { try { console.log("Voir plus de Formatrice"); } catch (error) { console.log(error) } } const dataFormatrice = [ { urlImage: require('../assets/marilyn-min.png'), name: 'OnglesMarilyn',nbrFormation:8,nbrAbonnement:382 }, { urlImage: require('../assets/monails-min.png'), name: 'Monails',nbrFormation:18,nbrAbonnement:165 }, ]; return ( <View style={{padding:10}}> <Card42 data={dataFormatrice} titleCard = {"Mes formatrices"} onPressItem={onPressItemCard4} onPressFormatrice={onPressFormatrice} urlEtoile={require('../assets/etoile.png')} index_component = {"formatrices"} /> </View> ) } const TabContent3 = ({userRole, user, isGoogleLogin, userId, updateCounter, setUser}) => { const navigation = useNavigation(); const renderInformationItem = (label, value, isEmail, isFirstName, isLastName) => { const goToEdit = () => { navigation.navigate('EditProfile', { label, value, userId }); } if (isEmail && isGoogleLogin || isFirstName && isGoogleLogin || isLastName && isGoogleLogin) { return ( <View style={styles.item}> <View style={{ flex: 1, flexDirection:'row' }}> <CustomText style={styles.label}>{label}</CustomText> <CustomText style={styles.value}>{value}</CustomText> </View> </View> ); } return ( <TouchableOpacity style={styles.item} onPress={goToEdit}> <View style={{ flex: 1, flexDirection:'row' }}> <CustomText style={styles.label}>{label}</CustomText> <CustomText style={styles.value}>{value}</CustomText> </View> <FontAwesomeIcon icon={faChevronRight} size={15} color="#b09c9f" /> </TouchableOpacity> ); } return ( <ScrollView contentContainerStyle={styles.container}> <View style={styles.settings1}> <CustomText style={{textAlign:'left', marginBottom:10, color:'#b09c9f', fontSize:15}}>Informations</CustomText> {renderInformationItem("Nom", user.last_name, false, false, true)} {renderInformationItem("Prénom", user.first_name, false, true, false)} {renderInformationItem("Pseudo", user.pseudo)} {renderInformationItem("Adresse e-mail", user.email, true, false, false)} {renderInformationItem("N° de téléphone",user.phone ? "0" + user.phone: '')} {renderInformationItem("Ville", user.city)} </View> <View style={styles.settings1}> <CustomText style={{textAlign:'left', marginBottom:10, color:'#b09c9f', fontSize:15}}>Réseaux sociaux</CustomText> {renderInformationItem("Instagram", user.instagram_link)} {renderInformationItem("Tiktok", user.tiktok_link)} </View> </ScrollView> ); } function ProfileScreen() { const navigation = useNavigation(); const route = useRoute(); const [userEmail, setUserEmail] = useState(''); const [code, setCode] = useState(''); const [codes, setCodes] = useState(''); const [userId, setUserId] = useState(''); const [user, setUser] = useState(''); const [message, setMessage] = useState(null); const [mode, setMode] = useState(''); const [profilePic, setProfilePic] = useState(null); const [modalVisible, setModalVisible] = useState(false) const [selectedImage, setSelectedImage] = useState(null); const [pseudo, setPseudo] = useState(null); const { userRole } = useAuth(); const [profileUpdateCounter, setProfileUpdateCounter] = useState(0); const [tab3UpdateCounter, setTab3UpdateCounter] = useState(0); const { isGoogleLogin } = useAuth() console.log('Google login: ',isGoogleLogin) const renderScene = ({ route }) => { switch (route.key) { case 'réalisations': return <TabContent1 userId={userId} />; case 'formatrices': return <TabContent2 />; case 'informations': return <TabContent3 userId={userId} userRole={userRole} user={user} isGoogleLogin={isGoogleLogin} updateCounter={tab3UpdateCounter} setUser={setUser} />; default: return null; } }; const [index, setIndex] = useState(0); const [routes] = useState([ { key: 'réalisations', title: 'RÉALISATIONS' }, { key: 'formatrices', title: 'FORMATRICES' }, { key: 'informations', title: 'INFORMATIONS' }, ]); useEffect(() => { const requestPermissions = async () => { try { const cameraPermission = await request(PERMISSIONS.IOS.CAMERA); if (cameraPermission === RESULTS.GRANTED) { console.log('Camera permission granted'); } else { console.log('Camera permission denied'); } const photoLibraryPermission = await request(PERMISSIONS.IOS.PHOTO_LIBRARY); if (photoLibraryPermission === RESULTS.GRANTED) { console.log('Photo library permission granted'); } else { console.log('Photo library permission denied'); } } catch (error) { console.error('Error requesting permissions:', error); } }; requestPermissions(); }, []); useEffect(() => { const fetchUser = async () => { const email = await AsyncStorage.getItem('email'); if (email) { const result = await userAPI.checkUserExists(email); if (result.result) { console.log(result.user); setUser(result.user); setUserId(result.user._id); } } }; fetchUser(); }, [tab3UpdateCounter]); useEffect(() => { if (route.params?.updated) { setTab3UpdateCounter(prev => prev + 1); navigation.setParams({ updated: false }); } }, [route.params?.updated]); useEffect(() => { const fetchUserProfilePicture = async () => { try { const result = await userAPI.getProfilePicture(userId); if (result.result) { setProfilePic(result.profil_image_url); } else { console.error('Failed to fetch profile picture:', result.error); } } catch (error) { console.error('Error fetching profile picture:', error); } }; if (userId) { fetchUserProfilePicture(); } }, [userId, profileUpdateCounter]); const toggleSnapModal = () => { setModalVisible(!modalVisible); }; const handleChoosePhoto = () => { return new Promise((resolve, reject) => { const options = { noData: true }; launchImageLibrary(options, (response) => { if (response.didCancel) { console.log('User cancelled image picker'); resolve(null); } else if (response.error) { console.log('ImagePicker Error:', response.error); reject(response.error); } else { resolve(response.assets[0].uri); } }); }); }; // Other functions return ( <ScrollView contentContainerStyle={styles.container}> <View style={styles.settingsIcon}> <TouchableOpacity onPress={handleSettings} style={styles.settingsButton}> <Image source={require('../assets/settings.png')} style={styles.settingsImage} /> </TouchableOpacity> </View> <View style={styles.header}> <View> <Image source={{ uri: profilePic && isAbsoluteUrl(profilePic) ? profilePic : process.env.API_URL2 + profilePic }} style={styles.profileImage} /> <TouchableOpacity style={styles.snapButton} onPress={toggleSnapModal}> <Image source={require('../assets/snap.png')} style={styles.snapImage} /> </TouchableOpacity> </View> <View style={styles.userInfo}> <CustomText style={styles.userName}>{user.first_name} {user.last_name}</CustomText> <View style={styles.premiumContainer}> <Image source={require('../assets/crown.png')} style={styles.icon} /> <CustomText style={styles.premiumText}>Premium Inactif</CustomText> </View> <View style={styles.sponsorshipContainer}> <Image source={require('../assets/users-pink.png')} style={styles.icon2} /> <CustomText style={styles.sponsorshipText}>Parrainage</CustomText> </View> </View> </View> <CustomButton title={'ESSAI PRO GRATUIT'} buttonStyle={styles.trialButton} /> <View style={styles.statsContainer}> <View style={styles.statBox}> <Image source={require('../assets/school-r.png')} style={styles.statImage} /> <CustomText style={styles.statValue}>38</CustomText> <CustomText style={styles.statLabel}>FORMATIONS</CustomText> </View> <View style={styles.statBox}> <Image source={require('../assets/vernis.png')} style={styles.statImage} /> <CustomText style={styles.statValue}>Experte</CustomText> <CustomText style={styles.statLabel}>BADGE</CustomText> </View> <View style={styles.statBox}> <Image source={require('../assets/crystal.png')} style={styles.statImage} /> <CustomText style={styles.statValue}>3840</CustomText> <CustomText style={styles.statLabel}>POINTS GAGNÉS</CustomText> </View> </View> <View> <View style={styles.tabViewContainer}> <TabView navigationState={{ index, routes }} renderScene={renderScene} onIndexChange={setIndex} initialLayout={{ width: screenWidth, height: screenHeight }} renderTabBar={props => ( <TabBar {...props} indicatorStyle={{ backgroundColor: '#f0738a', alignSelf: 'center', height: '10%', marginRight: '-22.5%', marginLeft: '6.5%', borderRadius: 10, }} style={{ backgroundColor: 'white', height: 60, elevation: 0, borderBottomWidth: 1, borderBottomColor: 'lightgrey', justifyContent: 'center', }} renderLabel={({ route, focused }) => ( <CustomText style={{ color: focused ? 'black' : '#b09c9f', marginTop: 8, fontFamily: 'Poppins-Medium', fontSize: 14, marginBottom: 12, width: '100%' }}> {route.title} </CustomText> )} /> )} /> </View> </View> <Modal animationType="slide" transparent={true} visible={modalVisible} onRequestClose={toggleSnapModal} > <View style={styles.modalBackdrop}> <View style={[styles.modalView, { height: screenHeight * 0.4 }]}> <TouchableOpacity onPress={toggleSnapModal}> <CustomText style={styles.modalCloseText}>Retour</CustomText> </TouchableOpacity> <View style={{flexDirection:'column', alignItems:'center', justifyContent:'center',}}> <CustomText style={{fontFamily:'Poppins-SemiBold', fontSize:20, textAlign:'center', width:'75%', marginTop:'7.5%'}}>Modification de la photo de profil</CustomText> <CustomButton buttonStyle={styles.btnStyle} textStyle={styles.txtBtnStyle} title={"Choisir une photo"} imageSource={require('../assets/photo-snap.png')} iconImageStyle={{width:20, height:20}} onPress={handleGalleryPick} /> <CustomButton buttonStyle={styles.btnStyle} textStyle={styles.txtBtnStyle} title={"Prendre un selfie"} imageSource={require('../assets/camera-snap.png')} iconImageStyle={{width:20, height:20}} onPress={handleTakeSelfie} /> </View> </View> </View> </Modal> </ScrollView> ); } export default ProfileScreen;
2.CustomComponents.jsx:
// CustomComponents.js import { TouchableOpacity, Text, TextInput, View, StyleSheet, Image } from 'react-native'; import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'; import { faEye, faEyeSlash, faChevronRight } from '@fortawesome/free-solid-svg-icons'; const CustomButton = ({ onPress, title, buttonStyle, textStyle, imageSource, iconImageStyle, styleType = 'primary', disabled}) => { const baseButtonStyle = defaultButtonStyle[styleType] || defaultButtonStyle.primary; return ( <TouchableOpacity onPress={onPress} style={[baseButtonStyle, buttonStyle]} disabled={disabled}> <Text style={[defaultButtonTextStyle, textStyle]}>{title}</Text> {imageSource && <Image source={imageSource} style={iconImageStyle} />} </TouchableOpacity> ); }; const CustomText = ({ style, children }) => { return <Text style={[defaultTextStyle, style]}>{children}</Text>; }; const CustomInput = ({ placeholder, onChangeText, value, error,success, onFocus, onBlur, secureTextEntry, style }) => { return ( <View style={[defaultInputStyle.container, style]}> <TextInput style={defaultInputStyle.input} placeholderTextColor="#999" placeholder={placeholder} onChangeText={onChangeText} value={value} onFocus={onFocus} onBlur={onBlur} secureTextEntry={secureTextEntry} /> {error && <Text style={defaultInputStyle.error}>{error}</Text>} {success && <Text style={defaultInputStyle.success}>{success}</Text>} </View> ); }; export { CustomButton, CustomText, CustomInput };