From 009f1ec792052b358d92dc7389ff764bb9bce2db Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Fri, 20 Feb 2026 19:25:38 -0500 Subject: [PATCH] Gracefully handle backend failures in Expo app --- API.js | 119 +++++++++++++++++++++++++++---- App.js | 1 + Views/Courses.js | 2 +- Views/Feed.js | 14 ++-- Views/Menu.js | 11 +-- Views/Profile.js | 15 ++-- Views/ProfileSettings.js | 20 +++--- components/CourseCard.js | 30 ++++---- components/GroupCard.js | 20 +++--- components/Post.js | 1 - components/PostPopularUsers.js | 21 +++--- components/ProfileCard.js | 22 +++--- components/ProfileHeader.js | 16 +++-- components/ProfileSmallHeader.js | 10 +-- 14 files changed, 205 insertions(+), 97 deletions(-) diff --git a/API.js b/API.js index d0e136b..9b8689a 100644 --- a/API.js +++ b/API.js @@ -1,6 +1,38 @@ const baseUrl = "https://emiapi.reynafamily.com"; //const baseUrl = "http://localhost:3000"; +const requestErrorCooldownMs = 30000; +const profileFailureCooldownMs = 60000; +const recentRequestErrors = {}; +const failedProfileCache = {}; + +const normalizePath = (path = "") => { + return path.replace(/[a-f0-9]{24}/gi, ":id"); +}; + +const logRequestErrorOnce = (key, message) => { + const now = Date.now(); + if (recentRequestErrors[key] && now - recentRequestErrors[key] < requestErrorCooldownMs) return; + recentRequestErrors[key] = now; + console.error(message); +}; + +const getFallbackForPath = (path = "") => { + if (path.startsWith("/post/") || path === "/post/") return []; + return {}; +}; + +const parseJsonResponse = async (response, path = "", fallback) => { + const text = await response.text(); + if (!text) return fallback; + try { + return JSON.parse(text); + } catch (error) { + const normalizedPath = normalizePath(path); + logRequestErrorOnce(`invalid-json:${normalizedPath}`, `Invalid JSON from ${normalizedPath}: ${text.slice(0, 200)}`); + return fallback; + } +}; let getCall = async (path = "", params = {}) => { let queryParams = "?"; @@ -8,6 +40,7 @@ let getCall = async (path = "", params = {}) => { queryParams += p + "=" + params[p] + "&" }); let localBaseUrl = global.baseUrl ?? baseUrl; + const fallback = getFallbackForPath(path); return fetch(localBaseUrl + path + queryParams, { method: 'GET', mode: 'cors', @@ -15,9 +48,17 @@ let getCall = async (path = "", params = {}) => { headers: { 'Content-Type': 'application/json', } - }).then(response => response.json()).catch((error) => { - console.error(error); - console.trace(); + }).then(async (response) => { + const normalizedPath = normalizePath(path); + if (!response.ok) { + logRequestErrorOnce(`GET:${normalizedPath}:${response.status}`, `GET ${normalizedPath} failed: ${response.status}`); + return fallback; + } + return parseJsonResponse(response, path, fallback); + }).catch((error) => { + const normalizedPath = normalizePath(path); + logRequestErrorOnce(`GET:${normalizedPath}:network`, `GET ${normalizedPath} network error: ${error?.message || error}`); + return fallback; }) } @@ -27,6 +68,7 @@ let deleteCall = async (path = "", params = {}) => { queryParams += p + "=" + params[p] + "&" }); let localBaseUrl = global.baseUrl ?? baseUrl; + const fallback = {}; return fetch(localBaseUrl + path + queryParams, { method: 'DELETE', mode: 'cors', @@ -34,14 +76,23 @@ let deleteCall = async (path = "", params = {}) => { headers: { 'Content-Type': 'application/json', } - }).then(response => response.json()).catch((error) => { - console.error(error); - console.trace(); + }).then(async (response) => { + const normalizedPath = normalizePath(path); + if (!response.ok) { + logRequestErrorOnce(`DELETE:${normalizedPath}:${response.status}`, `DELETE ${normalizedPath} failed: ${response.status}`); + return fallback; + } + return parseJsonResponse(response, path, fallback); + }).catch((error) => { + const normalizedPath = normalizePath(path); + logRequestErrorOnce(`DELETE:${normalizedPath}:network`, `DELETE ${normalizedPath} network error: ${error?.message || error}`); + return fallback; }) } let postCall = async (path, params) => { let localBaseUrl = global.baseUrl ?? baseUrl; + const fallback = {}; return fetch(localBaseUrl + path, { method: 'POST', mode: 'cors', @@ -50,9 +101,17 @@ let postCall = async (path, params) => { headers: { 'Content-Type': 'application/json', } - }).then(response => response.json()).catch((error) => { - console.error(error); - console.trace(); + }).then(async (response) => { + const normalizedPath = normalizePath(path); + if (!response.ok) { + logRequestErrorOnce(`POST:${normalizedPath}:${response.status}`, `POST ${normalizedPath} failed: ${response.status}`); + return fallback; + } + return parseJsonResponse(response, path, fallback); + }).catch((error) => { + const normalizedPath = normalizePath(path); + logRequestErrorOnce(`POST:${normalizedPath}:network`, `POST ${normalizedPath} network error: ${error?.message || error}`); + return fallback; }) } @@ -61,14 +120,44 @@ let CurrentProfile; let CurrentProfileData; let userNameCache = {}; //save this on localstorage let working_on = {}; //Promises +const emptyProfileData = (id = "") => ({ + _id: id || "", + profile: { + firstName: "", + lastName: "", + photo: "", + description: "", + language: "en", + }, + data: {}, + following: [], +}); let getProfileFromCache = async (id, refresh=false) => { if(!id) console.trace(); + const lastFailure = failedProfileCache[id] || 0; + if (!refresh && lastFailure && Date.now() - lastFailure < profileFailureCooldownMs) { + return emptyProfileData(id); + } if (userNameCache[id] && !refresh) return userNameCache[id]; if (working_on[id] && !refresh) return working_on[id]; - //console.log(id, "not in cache, getting...") - working_on[id] = getCall("/user/" + id) + working_on[id] = getCall("/user/" + id).then((profile) => { + const hasRealProfile = !!(profile && profile._id); + const safeProfile = hasRealProfile ? { ...emptyProfileData(id), ...profile, profile: { ...emptyProfileData(id).profile, ...(profile.profile || {}) } } : emptyProfileData(id); + if (hasRealProfile) { + userNameCache[id] = safeProfile; + delete failedProfileCache[id]; + } else { + failedProfileCache[id] = Date.now(); + } + delete working_on[id]; + return safeProfile; + }).catch(() => { + failedProfileCache[id] = Date.now(); + delete working_on[id]; + return emptyProfileData(id); + }); return working_on[id]; } @@ -140,8 +229,8 @@ const API = { }, //Posts getPosts(userid) { - if (userid) return getCall("/post/usr/" + userid); - return getCall("/post/"); + if (userid) return getCall("/post/usr/" + userid).then((data) => Array.isArray(data) ? data : []); + return getCall("/post/").then((data) => Array.isArray(data) ? data : []); }, getPostsByTag(tag) { return getCall("/post/tag/" + tag); @@ -235,7 +324,7 @@ const API = { }); }, getUserProfile(profileid, refresh=false) { - return getProfileFromCache(profileid, refresh); + return getProfileFromCache(profileid, refresh).then((profile) => profile || emptyProfileData(profileid)); }, deleteProfile(profileid){ return deleteCall("/user/" + profileid); @@ -325,4 +414,4 @@ const API = { } } -export default API; \ No newline at end of file +export default API; diff --git a/App.js b/App.js index 42cc5c8..be5dbf3 100644 --- a/App.js +++ b/App.js @@ -102,6 +102,7 @@ const MainNavigation = ({ route }) => { registerForPushNotificationsAsync().then(async (token) => { let isLoggedIn = await API.isLoggedIn(); if (!isLoggedIn) return false; + if (!token) return false; API.registerToken(token); return setExpoPushToken(token); }); diff --git a/Views/Courses.js b/Views/Courses.js index c575a9d..865a0ca 100644 --- a/Views/Courses.js +++ b/Views/Courses.js @@ -142,7 +142,7 @@ const Courses = () => { horizontal={true} data={watching} renderItem={watchingCourse} - keyExtractor={item => item.profile._id} + keyExtractor={(item, index) => item?.profile?._id || item?._id || `watching-${index}`} initialNumToRender={2} /> : <> diff --git a/Views/Feed.js b/Views/Feed.js index 0a85ae2..8ceb543 100644 --- a/Views/Feed.js +++ b/Views/Feed.js @@ -49,6 +49,7 @@ const handleURL = (url, navigation) => { } async function onFetchUpdateAsync() { + if (__DEV__) return; try { const update = await Updates.checkForUpdateAsync(); @@ -90,11 +91,11 @@ let Feed = ({ navigation, route }) => { } if (!route.params?.reRender) { API.getMe().then((me) => { - if (subscribed) { + if (subscribed && me && me._id) { GlobalState.me = me; posthog.identify(me.userid, { - name: me.profile.firstName, + name: me.profile?.firstName, profileid: me._id, is_superuser: me.superuser, } @@ -108,11 +109,12 @@ let Feed = ({ navigation, route }) => { console.log("Feed from server") } await onFetchUpdateAsync(); - flatListRef.current.scrollToOffset({ animated: true, offset: 0 }) + flatListRef.current?.scrollToOffset({ animated: true, offset: 0 }) let posts = await API.getPosts(); if (subscribed) { - setPosts(posts); - storeFeed(posts); + const safePosts = Array.isArray(posts) ? posts : []; + setPosts(safePosts); + storeFeed(safePosts); } console.log("Feed, end useEffect") } @@ -141,7 +143,7 @@ let Feed = ({ navigation, route }) => { //ListHeaderComponent={ setPosts([newPost, ...Posts])} />} refreshing={Posts.length === 0} onRefresh={() => { - API.getPosts().then(setPosts); + API.getPosts().then((data) => setPosts(Array.isArray(data) ? data : [])); }} initialNumToRender={3} maxToRenderPerBatch={3} diff --git a/Views/Menu.js b/Views/Menu.js index 3d0b29c..d6d4eb7 100644 --- a/Views/Menu.js +++ b/Views/Menu.js @@ -24,7 +24,7 @@ let MenuView = ({ navigation }) => { let getData = async () => { const r = await API.getMyProfiles(); if (!subscribed) return; - setMyProfiles(r.profiles); + setMyProfiles(Array.isArray(r?.profiles) ? r.profiles : []); } getData(); return () => { @@ -40,16 +40,17 @@ let MenuView = ({ navigation }) => { //reloadAppAsync(); } const profileLists = myProfiles.map((profile) => { + const profileInfo = profile?.profile || {}; const DefaultPhoto = "https://social.emmint.com/uploads/e6f9be6d665dc43417701bf16a90122c.png"; - let photoUrl = profile.profile?.photo ? 'https://social.emmint.com/' + profile.profile.photo : DefaultPhoto; + let photoUrl = profileInfo.photo ? 'https://social.emmint.com/' + profileInfo.photo : DefaultPhoto; let icon = profile._id ? (!profile.isGroup ? "person-outline" : "group") : ''; icon = icon === "person-outline" && profile.subscription && profile.subscription > (new Date() - 0) ? "assignment-ind" : icon; icon = icon === "group" && profile.isCourse ? "subscriptions" : icon; icon = icon === "group" && profile.isPrivate ? "screen-lock-portrait" : icon; return <> { await API.changeProfile(profile._id); GlobalState.me = await API.getMe(); @@ -103,4 +104,4 @@ let MenuView = ({ navigation }) => { ) } -export default MenuView; \ No newline at end of file +export default MenuView; diff --git a/Views/Profile.js b/Views/Profile.js index 97e1a31..b142ced 100644 --- a/Views/Profile.js +++ b/Views/Profile.js @@ -44,16 +44,17 @@ let Profile = ({ navigation, route }) => { console.log('Loading Cache Profile:' + route.params.profileid); await API.getUserProfile(route.params.profileid).then((profileObj) => { if(!subscribed) return 0; - let profile = profileObj.profile - setProfile(profileObj); - navigation.setOptions({ title: profile.firstName + " " + profile.lastName }); + const nextProfile = profileObj && profileObj._id ? profileObj : {}; + const profileData = nextProfile.profile || {}; + setProfile(nextProfile); + navigation.setOptions({ title: (profileData.firstName || "") + " " + (profileData.lastName || "") }); }); await getProfilePosts(route.params.profileid).then(setPosts); console.log('Loaded Cache Profile:' + route.params.profileid); API.getPosts(route.params.profileid).then((data) => { setLoading(false); if(!subscribed) return 0; - setPosts(data); + setPosts(Array.isArray(data) ? data : []); storeProfilePosts(route.params.profileid, data); console.log('Store Cache Profile:' + route.params.profileid); }); @@ -62,7 +63,7 @@ let Profile = ({ navigation, route }) => { console.log('Getting posts with tag', tag) API.getPostsWithTag(route.params.profileid, tag).then((data) => { if(!subscribed) return 0; - setPosts(data.posts); + setPosts(Array.isArray(data?.posts) ? data.posts : []); }); } else { // if no profile information is pressent should load feed @@ -80,7 +81,7 @@ let Profile = ({ navigation, route }) => { API.getPostsWithTag(tag).then((data) => { //if(!subscribed) return 0; console.log(data.posts); - setPosts(data.posts); + setPosts(Array.isArray(data?.posts) ? data.posts : []); }); } @@ -148,7 +149,7 @@ let Profile = ({ navigation, route }) => { maxToRenderPerBatch={3} removeClippedSubviews={true} onRefresh={() => { - API.getPosts(route.params.profileid).then(setPosts); + API.getPosts(route.params.profileid).then((data) => setPosts(Array.isArray(data) ? data : [])); }} /> : <> //TODO: Add empty profile card here diff --git a/Views/ProfileSettings.js b/Views/ProfileSettings.js index d53ce4a..d525343 100644 --- a/Views/ProfileSettings.js +++ b/Views/ProfileSettings.js @@ -16,13 +16,14 @@ import * as ImagePicker from 'expo-image-picker'; let ProfileSettings = () => { const gState = useSnapshot(GlobalState); const viewer = gState.me; + const viewerProfile = viewer?.profile || {}; const [photo, setPhoto] = React.useState(null); - const [name, setName] = React.useState(viewer.profile.firstName); - const [lastName, setLastName] = React.useState(viewer.profile.lastName); - const [photoUrl, setphotoUrl] = React.useState(viewer.profile.photo); - const [language, setLanguage] = React.useState(viewer.profile.language); + const [name, setName] = React.useState(viewerProfile.firstName || ""); + const [lastName, setLastName] = React.useState(viewerProfile.lastName || ""); + const [photoUrl, setphotoUrl] = React.useState(viewerProfile.photo || ""); + const [language, setLanguage] = React.useState(viewerProfile.language || "en"); const [updateKey, setUpdateKey] = React.useState(0); - const [description, setDescription] = React.useState(viewer.profile.description); + const [description, setDescription] = React.useState(viewerProfile.description || ""); const [uploading, setUploading] = React.useState(false); const pickImage = async () => { @@ -41,6 +42,7 @@ let ProfileSettings = () => { let newPhotoURL = await handleUploadPhoto(result.assets[0]); if (newPhotoURL !== "") { setphotoUrl(newPhotoURL); + if (!GlobalState.me.profile) GlobalState.me.profile = {}; GlobalState.me.profile.photo = newPhotoURL; updateProfile() setUpdateKey(updateKey + 1); @@ -90,9 +92,9 @@ let ProfileSettings = () => { }; let updateProfile = async () => { - let currentProfile = await API.getUserProfile(viewer._id) - currentData = currentProfile.data; - currentProfile = currentProfile.profile; + let currentProfile = await API.getUserProfile(viewer?._id); + const currentData = currentProfile?.data || {}; + currentProfile = currentProfile?.profile || {}; try { //let currentProfile = JSON.parse(JSON.stringify(viewer.profile)); currentProfile.firstName = name; @@ -204,4 +206,4 @@ let ProfileSettings = () => { ) } -export default ProfileSettings; \ No newline at end of file +export default ProfileSettings; diff --git a/components/CourseCard.js b/components/CourseCard.js index 4d0ffd9..ef712db 100644 --- a/components/CourseCard.js +++ b/components/CourseCard.js @@ -29,12 +29,16 @@ const getName = async (key) => { let CourseCard = ({ profileid, hideIcon, profileObj, twoCols }) => { let [profile, setProfile] = useState(profileObj || {}); + const safeProfile = profile || {}; + const safeProfileInfo = safeProfile.profile || {}; + const safeProfileObj = profileObj || {}; + const safeProfileObjInfo = safeProfileObj.profile || {}; const navigation = useNavigation(); useEffect(() => { let subscribed = true; const getData = async () => { - if (profileObj._id) return 0; + if (safeProfileObj._id) return 0; let cacheProfile = await getName(profileid); if (cacheProfile && cacheProfile.profile) setProfile(cacheProfile); let p = await API.getUserProfile(profileid).catch(() => { return {} }); @@ -47,13 +51,13 @@ let CourseCard = ({ profileid, hideIcon, profileObj, twoCols }) => { } }, [profileid]); - let icon = profile._id ? (!profile.isGroup ? "person-outline" : "group") : ''; - icon = icon === "person-outline" && profile.subscription && profile.subscription > (new Date() - 0) ? "assignment-ind" : icon; - icon = icon === "group" && profile.isCourse ? "subscriptions" : icon; - let photoUrl = profile.data && profile.data.profileImg ? profile.data.profileImg : DefaultPhoto; + let icon = safeProfile._id ? (!safeProfile.isGroup ? "person-outline" : "group") : ''; + icon = icon === "person-outline" && safeProfile.subscription && safeProfile.subscription > (new Date() - 0) ? "assignment-ind" : icon; + icon = icon === "group" && safeProfile.isCourse ? "subscriptions" : icon; + let photoUrl = safeProfile.data && safeProfile.data.profileImg ? safeProfile.data.profileImg : DefaultPhoto; const onPress = () => { - return navigation.navigate('Profile', { profileid: profile._id }) + return navigation.navigate('Profile', { profileid: safeProfile._id }) } return ( @@ -64,28 +68,28 @@ let CourseCard = ({ profileid, hideIcon, profileObj, twoCols }) => { {!hideIcon ? : <>} - {profile.profile && profile.profile.firstName} {profile.profile && profile.profile.lastName} + {safeProfileInfo.firstName} {safeProfileInfo.lastName} - {profileObj.profile.description ? profileObj.profile.description : "We are working on this course description, soon to come!"} + {safeProfileObjInfo.description ? safeProfileObjInfo.description : "We are working on this course description, soon to come!"} - {profile.data ? + {safeProfile.data ? - By: {profile.data.author ? profile.data.author : 'Working on this'} + By: {safeProfile.data.author ? safeProfile.data.author : 'Working on this'} - {profile.data.year ? profile.data.year : 'XXXX'} + {safeProfile.data.year ? safeProfile.data.year : 'XXXX'} - {profile.data.duration ? profile.data.duration : '??'} + {safeProfile.data.duration ? safeProfile.data.duration : '??'} - {profile.data.language} + {safeProfile.data.language} diff --git a/components/GroupCard.js b/components/GroupCard.js index 7e2ec35..6d9043e 100644 --- a/components/GroupCard.js +++ b/components/GroupCard.js @@ -31,12 +31,16 @@ const getName = async (key) => { let ProfileCard = ({ profileid, hideIcon, profileObj }) => { let [profile, setProfile] = useState(profileObj || {}); + const safeProfile = profile || {}; + const safeProfileInfo = safeProfile.profile || {}; + const safeProfileObj = profileObj || {}; + const safeProfileObjInfo = safeProfileObj.profile || {}; const navigation = useNavigation(); useEffect(() => { let subscribed = true; const getData = async () => { - if (profileObj._id) return 0; + if (safeProfileObj._id) return 0; let cacheProfile = await getName(profileid); if (cacheProfile && cacheProfile.profile) setProfile(cacheProfile); let p = await API.getUserProfile(profileid).catch(() => { return {} }); @@ -49,14 +53,14 @@ let ProfileCard = ({ profileid, hideIcon, profileObj }) => { } }, [profileid]); - let icon = profile._id ? (!profile.isGroup ? "person-outline" : "group") : ''; - icon = icon === "person-outline" && profile.subscription && profile.subscription > (new Date() - 0) ? "assignment-ind" : icon; - icon = icon === "group" && profile.isCourse ? "subscriptions" : icon; - icon = icon === "group" && profile.isPrivate ? "screen-lock-portrait" : icon; - let photoUrl = profile.profile.photo ? 'https://social.emmint.com/' + profile.profile.photo : DefaultPhoto; + let icon = safeProfile._id ? (!safeProfile.isGroup ? "person-outline" : "group") : ''; + icon = icon === "person-outline" && safeProfile.subscription && safeProfile.subscription > (new Date() - 0) ? "assignment-ind" : icon; + icon = icon === "group" && safeProfile.isCourse ? "subscriptions" : icon; + icon = icon === "group" && safeProfile.isPrivate ? "screen-lock-portrait" : icon; + let photoUrl = safeProfileInfo.photo ? 'https://social.emmint.com/' + safeProfileInfo.photo : DefaultPhoto; const onPress = () => { - return navigation.navigate('Profile', { profileid: profile._id }) + return navigation.navigate('Profile', { profileid: safeProfile._id }) } return ( @@ -70,7 +74,7 @@ let ProfileCard = ({ profileid, hideIcon, profileObj }) => { } - description={profileObj.profile.description} + description={safeProfileObjInfo.description || ""} //left={props => } titleStyle={{fontWeight:"bold", fontSize:20}} descriptionStyle={{}} diff --git a/components/Post.js b/components/Post.js index 1999cb6..9ede69f 100644 --- a/components/Post.js +++ b/components/Post.js @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import { Text, Pressable, FlatList, StyleSheet, View, Share, Alert, Linking } from 'react-native'; -import Hyperlink from 'react-native-hyperlink' import { Button, Card, Chip } from 'react-native-paper'; import API from './../API.js'; import UserName from './UserName.js'; diff --git a/components/PostPopularUsers.js b/components/PostPopularUsers.js index 3146e6c..ba7caf0 100644 --- a/components/PostPopularUsers.js +++ b/components/PostPopularUsers.js @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import { Text, ScrollView, FlatList, StyleSheet, View, Share } from 'react-native'; -import Hyperlink from 'react-native-hyperlink' import { Button, Card, Chip } from 'react-native-paper'; import API from '../API.js'; import UserName from './UserName.js'; @@ -44,17 +43,15 @@ let Post = (props) => { margin: 0, marginBottom: 0 }}> - - - {cleanContent} - - item} - /> - - + + {cleanContent} + + item} + /> + ); diff --git a/components/ProfileCard.js b/components/ProfileCard.js index 1181d42..2531874 100644 --- a/components/ProfileCard.js +++ b/components/ProfileCard.js @@ -30,12 +30,16 @@ const getName = async (key) => { let ProfileCard = ({ profileid, hideIcon, profileObj }) => { let [profile, setProfile] = useState(profileObj || {}); + const safeProfile = profile || {}; + const safeProfileInfo = safeProfile.profile || {}; + const safeProfileObj = profileObj || {}; + const safeProfileObjInfo = safeProfileObj.profile || {}; const navigation = useNavigation(); useEffect(() => { let subscribed = true; const getData = async () => { - if (profileObj._id) return 0; + if (safeProfileObj._id) return 0; let cacheProfile = await getName(profileid); if (cacheProfile && cacheProfile.profile && subscribed) setProfile(cacheProfile); let p = await API.getUserProfile(profileid).catch(() => { return {} }); @@ -48,13 +52,13 @@ let ProfileCard = ({ profileid, hideIcon, profileObj }) => { } }, [profileid]); - let icon = profile._id ? (!profile.isGroup ? "person-outline" : "group") : ''; - icon = icon === "person-outline" && profile.subscription && profile.subscription > (new Date() - 0) ? "assignment-ind" : icon; - icon = icon === "group" && profile.isCourse ? "subscriptions" : icon; - let photoUrl = profile.profile.photo ? 'https://social.emmint.com/' + profile.profile.photo : DefaultPhoto; + let icon = safeProfile._id ? (!safeProfile.isGroup ? "person-outline" : "group") : ''; + icon = icon === "person-outline" && safeProfile.subscription && safeProfile.subscription > (new Date() - 0) ? "assignment-ind" : icon; + icon = icon === "group" && safeProfile.isCourse ? "subscriptions" : icon; + let photoUrl = safeProfileInfo.photo ? 'https://social.emmint.com/' + safeProfileInfo.photo : DefaultPhoto; const onPress = () => { - return navigation.navigate('Profile', { profileid: profile._id }) + return navigation.navigate('Profile', { profileid: safeProfile._id }) } return ( @@ -63,11 +67,11 @@ let ProfileCard = ({ profileid, hideIcon, profileObj }) => { <Text> - {profile.profile && profile.profile.firstName} {profile.profile && profile.profile.lastName} + {safeProfileInfo.firstName} {safeProfileInfo.lastName} </Text> - {profileObj.profile.description} - + {safeProfileObjInfo.description || ""} + diff --git a/components/ProfileHeader.js b/components/ProfileHeader.js index 63d73ca..623a4a0 100644 --- a/components/ProfileHeader.js +++ b/components/ProfileHeader.js @@ -7,7 +7,9 @@ import FollowButton from './basics/FollowButton'; const DefaultPhoto = "https://social.emmint.com/uploads/e6f9be6d665dc43417701bf16a90122c.png"; const ProfileHeader = ({ profileObj }) => { - let photoUrl = profileObj.profile && profileObj.profile.photo ? 'https://social.emmint.com/' + profileObj.profile.photo + '?width=1000&height=1000' : DefaultPhoto; + const safeProfileObj = profileObj || {}; + const safeProfile = safeProfileObj.profile || {}; + let photoUrl = safeProfile.photo ? 'https://social.emmint.com/' + safeProfile.photo + '?width=1000&height=1000' : DefaultPhoto; return ( <> @@ -16,9 +18,9 @@ const ProfileHeader = ({ profileObj }) => { }} /> - <UserName profileid={profileObj._id} /> + <UserName profileid={safeProfileObj._id} /> - {profileObj.profile.description} + {safeProfile.description || ""} { borderRadius: 25, opacity: 0.7 }}> - + { }}> @@ -63,4 +65,4 @@ const ProfileHeader = ({ profileObj }) => { } -export default React.memo(ProfileHeader); \ No newline at end of file +export default React.memo(ProfileHeader); diff --git a/components/ProfileSmallHeader.js b/components/ProfileSmallHeader.js index 979f7cc..bcb2c41 100644 --- a/components/ProfileSmallHeader.js +++ b/components/ProfileSmallHeader.js @@ -6,12 +6,14 @@ import UserName from './UserName'; const DefaultPhoto = "https://social.emmint.com/uploads/e6f9be6d665dc43417701bf16a90122c.png"; const ProfileSmallHeader = ({ profileObj }) => { - let photoUrl = profileObj.profile.photo ? 'https://social.emmint.com/' + profileObj.profile.photo : DefaultPhoto; + const safeProfileObj = profileObj || {}; + const safeProfile = safeProfileObj.profile || {}; + let photoUrl = safeProfile.photo ? 'https://social.emmint.com/' + safeProfile.photo : DefaultPhoto; return ( <> } - subtitle={profileObj.profile.description} + title={} + subtitle={safeProfile.description || ""} left={(props) => } right={(props) => { }} />} /> @@ -20,4 +22,4 @@ const ProfileSmallHeader = ({ profileObj }) => { } -export default React.memo(ProfileSmallHeader); \ No newline at end of file +export default React.memo(ProfileSmallHeader);