From 0fd892ad288f2e1eaaa4fdf5e1fd6f15dbd45860 Mon Sep 17 00:00:00 2001 From: Ashm Walia <40498934+ashmgarv@users.noreply.github.com> Date: Fri, 4 Dec 2020 08:50:24 -0800 Subject: [TMA - 398 AND TMA-430] Replace Providers with Redux Store (#125) * First * WIP * Thunk * Some more comments * sc * recent searches and follounfollow * Edit profile dummy * Block / unblock and some cleanup * Replace auth provider * Sc * Delete AP after rebase * Discover users * Cleanup * More cleanup * Replace profile provider * Fixed build failure * Fixed a bug reported * Prevent app crash when backend server is down --- src/components/profile/Avatar.tsx | 18 ++-- src/components/profile/Content.tsx | 167 +++++++++++++++++------------- src/components/profile/Cover.tsx | 19 ++-- src/components/profile/FollowCount.tsx | 34 +++--- src/components/profile/Followers.tsx | 10 +- src/components/profile/MoreInfoDrawer.tsx | 6 +- src/components/profile/ProfileBody.tsx | 23 ++-- src/components/profile/ProfileHeader.tsx | 48 ++++----- src/components/profile/ProfilePreview.tsx | 30 ++++-- 9 files changed, 207 insertions(+), 148 deletions(-) (limited to 'src/components/profile') diff --git a/src/components/profile/Avatar.tsx b/src/components/profile/Avatar.tsx index aca3bf4d..903d0d18 100644 --- a/src/components/profile/Avatar.tsx +++ b/src/components/profile/Avatar.tsx @@ -1,16 +1,20 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {Image, StyleSheet} from 'react-native'; -import {AuthContext, ProfileContext} from '../../routes/'; +import {useSelector} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {ScreenType} from '../../types'; const PROFILE_DIM = 100; interface AvatarProps { style: object; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Avatar: React.FC = ({style, isProfileView}) => { - const {avatar} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); +const Avatar: React.FC = ({style, screenType, userXId}) => { + const {avatar} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + return ( ; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Content: React.FC = ({y, isProfileView}) => { - const [profileBodyHeight, setProfileBodyHeight] = useState(0); - const {user, moments, followers, following, updateFollowers} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); - - const { - logout, - user: loggedInUser, - updateFollowers: updateLoggedInUserFollowers, - blockedUsers, - updateBlockedUsers, - } = React.useContext(AuthContext); +const Content: React.FC = ({y, userXId, screenType}) => { + const dispatch = useDispatch(); + + const {user = NO_USER, profile = NO_PROFILE} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + + const {followers = EMPTY_PROFILE_PREVIEW_LIST} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.follow); + + const {moments = EMPTY_MOMENTS_LIST} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.moments); + + const {blockedUsers = EMPTY_PROFILE_PREVIEW_LIST} = useSelector( + (state: RootState) => state.blocked, + ); + const {user: loggedInUser = NO_USER} = useSelector( + (state: RootState) => state.user, + ); + const state = useStore().getState(); /** * States @@ -38,8 +65,9 @@ const Content: React.FC = ({y, isProfileView}) => { const [imagesMap, setImagesMap] = useState>( new Map(), ); - const [isFollowed, setIsFollowed] = React.useState(false); - const [isBlocked, setIsBlocked] = React.useState(false); + const [isFollowed, setIsFollowed] = useState(false); + const [isBlocked, setIsBlocked] = useState(false); + const [profileBodyHeight, setProfileBodyHeight] = useState(0); /** * If own profile is being viewed then do not show the follow button. @@ -51,8 +79,6 @@ const Content: React.FC = ({y, isProfileView}) => { setProfileBodyHeight(height); }; - const {userId} = user; - const createImagesMap = useCallback(() => { var map = new Map(); moments.forEach(function (imageObject) { @@ -68,9 +94,6 @@ const Content: React.FC = ({y, isProfileView}) => { }, [moments]); useEffect(() => { - if (!userId) { - return; - } createImagesMap(); }, [createImagesMap]); @@ -78,9 +101,6 @@ const Content: React.FC = ({y, isProfileView}) => { * This hook is called on load of profile and when you update the followers list. */ useEffect(() => { - if (!userId) { - return; - } const isActuallyFollowed = followers.some( (follower) => follower.username === loggedInUser.username, ); @@ -90,62 +110,63 @@ const Content: React.FC = ({y, isProfileView}) => { }, [followers]); useEffect(() => { - if (!userId) { - return; - } - const isActuallyBlocked = blockedUsers.some( (cur_user) => user.username === cur_user.username, ); if (isBlocked != isActuallyBlocked) { setIsBlocked(isActuallyBlocked); } - }, [blockedUsers]); + }, [blockedUsers, user]); + + /** + * The object returned by this method is added to the list of blocked / followed users by the reducer. + * Which helps us prevent an extra api call to the backend just to fetch a user. + */ + const getUserAsProfilePreviewType = ( + passedInUser: UserType, + passedInProfile: ProfileType, + ): ProfilePreviewType => { + const fullName = passedInProfile.name.split(' '); + return { + id: passedInUser.userId, + username: passedInUser.username, + first_name: fullName[0], + last_name: fullName[1], + }; + }; /** * Handles a click on the follow / unfollow button. - * updateFollowers and updateLoggedInUerFollowers to make sure that we update followers list / count for both the users in context. + * followUnfollowUser takes care of updating the following list for loggedInUser + * updateUserXFollowersAndFollowing updates followers and following list for the followed user. */ + const handleFollowUnfollow = async () => { - const token = await AsyncStorage.getItem('token'); - if (!token) { - logout(); - return; - } - const isUpdatedSuccessful = await followOrUnfollowUser( - loggedInUser.userId, - userId, - token, - isFollowed, + await dispatch( + followUnfollowUser( + loggedInUser, + getUserAsProfilePreviewType(user, profile), + isFollowed, + ), ); - if (isUpdatedSuccessful) { - setIsFollowed(!isFollowed); - updateFollowers(true); - updateLoggedInUserFollowers(true); - } + await dispatch(updateUserXFollowersAndFollowing(user.userId, state)); }; /** * Handles a click on the block / unblock button. + * loadFollowData updates followers / following list for the logged in user + * updateUserXFollowersAndFollowing updates followers and following list for the followed user. */ const handleBlockUnblock = async () => { - const token = await AsyncStorage.getItem('token'); - if (!token) { - logout(); - return; - } - const isUpdatedSuccessful = await blockOrUnblockUser( - loggedInUser.userId, - userId, - token, - isBlocked, + await dispatch( + blockUnblockUser( + loggedInUser, + getUserAsProfilePreviewType(user, profile), + isBlocked, + ), ); - if (isUpdatedSuccessful) { - setIsBlocked(!isBlocked); - updateBlockedUsers(true); - updateFollowers(true); - updateLoggedInUserFollowers(true); - } + await dispatch(loadFollowData(loggedInUser.userId)); + await dispatch(updateUserXFollowersAndFollowing(user.userId, state)); }; return ( @@ -155,15 +176,12 @@ const Content: React.FC = ({y, isProfileView}) => { showsVerticalScrollIndicator={false} scrollEventThrottle={1}> - + = ({y, isProfileView}) => { handleBlockUnblock, }} /> - + {defaultMoments.map((title, index) => ( ))} diff --git a/src/components/profile/Cover.tsx b/src/components/profile/Cover.tsx index 36e41776..3c0f7045 100644 --- a/src/components/profile/Cover.tsx +++ b/src/components/profile/Cover.tsx @@ -1,18 +1,23 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {Image, StyleSheet} from 'react-native'; import Animated from 'react-native-reanimated'; import {IMAGE_WIDTH, COVER_HEIGHT, IMAGE_HEIGHT} from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes/'; +import {useSelector, useStore} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {ScreenType} from '../../types'; +import {DUMMY_USERID, NO_USER_DATA} from '../../store/initialStates'; const {interpolate, Extrapolate} = Animated; interface CoverProps { y: Animated.Value; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Cover: React.FC = ({y, isProfileView}) => { - const {cover} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); +const Cover: React.FC = ({y, userXId, screenType}) => { + const {cover = ''} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + const scale: Animated.Node = interpolate(y, { inputRange: [-COVER_HEIGHT, 0], outputRange: [1.5, 1.25], diff --git a/src/components/profile/FollowCount.tsx b/src/components/profile/FollowCount.tsx index 3e270428..a23a3533 100644 --- a/src/components/profile/FollowCount.tsx +++ b/src/components/profile/FollowCount.tsx @@ -1,24 +1,33 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {View, Text, StyleSheet, ViewProps} from 'react-native'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {useNavigation} from '@react-navigation/native'; -import {AuthContext, ProfileContext} from '../../routes'; +import {RootState} from '../../store/rootReducer'; +import {useSelector} from 'react-redux'; +import {ScreenType} from '../../types'; +import {EMPTY_PROFILE_PREVIEW_LIST} from '../../store/initialStates'; interface FollowCountProps extends ViewProps { mode: 'followers' | 'following'; - count: number; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } const FollowCount: React.FC = ({ style, mode, - count, - isProfileView, + userXId, + screenType, }) => { - const {followers, following} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); + const { + followers = EMPTY_PROFILE_PREVIEW_LIST, + following = EMPTY_PROFILE_PREVIEW_LIST, + } = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.follow); + + const isFollowers = mode === 'followers'; + const count = isFollowers ? followers.length : following.length; const navigation = useNavigation(); const displayed: string = @@ -33,14 +42,15 @@ const FollowCount: React.FC = ({ navigation.push('FollowersListScreen', { - isFollowers: mode === 'followers', - list: mode === 'followers' ? followers : following, + isFollowers, + userXId, + screenType, }) }> {displayed} - {mode === 'followers' ? 'Followers' : 'Following'} + {isFollowers ? 'Followers' : 'Following'} diff --git a/src/components/profile/Followers.tsx b/src/components/profile/Followers.tsx index e11041d0..c665603d 100644 --- a/src/components/profile/Followers.tsx +++ b/src/components/profile/Followers.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {View, StyleSheet, ViewProps, Text} from 'react-native'; -import {ProfilePreviewType} from '../../types'; +import {ProfilePreviewType, ScreenType} from '../../types'; import {ProfilePreview} from '..'; import {useNavigation} from '@react-navigation/native'; import {Button} from 'react-native-elements'; @@ -8,9 +8,14 @@ import {Button} from 'react-native-elements'; interface FollowersListProps { result: Array; sectionTitle: string; + screenType: ScreenType; } -const Followers: React.FC = ({result, sectionTitle}) => { +const Followers: React.FC = ({ + result, + sectionTitle, + screenType, +}) => { const navigation = useNavigation(); return ( <> @@ -31,6 +36,7 @@ const Followers: React.FC = ({result, sectionTitle}) => { key={profilePreview.id} {...{profilePreview}} previewType={'Comment'} + screenType={screenType} /> ))} diff --git a/src/components/profile/MoreInfoDrawer.tsx b/src/components/profile/MoreInfoDrawer.tsx index 719c1894..a8908b4d 100644 --- a/src/components/profile/MoreInfoDrawer.tsx +++ b/src/components/profile/MoreInfoDrawer.tsx @@ -4,14 +4,14 @@ import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {TAGG_TEXT_LIGHT_BLUE} from '../../constants'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; -import {AuthContext} from '../../routes'; import {BottomDrawer} from '../common'; import PersonOutline from '../../assets/ionicons/person-outline.svg'; +import {useSelector} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; interface MoreInfoDrawerProps { isOpen: boolean; setIsOpen: (visible: boolean) => void; - isProfileView: boolean; } const MoreInfoDrawer: React.FC = (props) => { @@ -20,7 +20,7 @@ const MoreInfoDrawer: React.FC = (props) => { const navigation = useNavigation(); const { user: {userId, username}, - } = useContext(AuthContext); + } = useSelector((state: RootState) => state.user); const goToEditProfile = () => { navigation.push('EditProfile', { diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx index c0253533..3c05fc26 100644 --- a/src/components/profile/ProfileBody.tsx +++ b/src/components/profile/ProfileBody.tsx @@ -1,33 +1,38 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {StyleSheet, View, Text, LayoutChangeEvent} from 'react-native'; import {TAGG_DARK_BLUE, TOGGLE_BUTTON_TYPE} from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes/'; import ToggleButton from './ToggleButton'; +import {RootState} from '../../store/rootReducer'; +import {useSelector} from 'react-redux'; +import {ScreenType} from '../../types'; +import {NO_PROFILE} from '../../store/initialStates'; interface ProfileBodyProps { onLayout: (event: LayoutChangeEvent) => void; - isProfileView: boolean; isFollowed: boolean; isBlocked: boolean; isOwnProfile: boolean; handleFollowUnfollow: Function; handleBlockUnblock: Function; + userXId: string; + screenType: ScreenType; } const ProfileBody: React.FC = ({ onLayout, - isProfileView, isFollowed, isBlocked, isOwnProfile, handleFollowUnfollow, handleBlockUnblock, + userXId, + screenType, }) => { const { - profile, + profile = NO_PROFILE, user: {username}, - } = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); + } = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); const {biography, website} = profile; @@ -36,7 +41,7 @@ const ProfileBody: React.FC = ({ {`@${username}`} {`${biography}`} {`${website}`} - {isProfileView && !isOwnProfile ? ( + {userXId && !isOwnProfile ? ( {!isBlocked && ( = ({ - isProfileView, - numFollowing, - numFollowers, -}) => { - const { - profile: {name}, - } = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); +const ProfileHeader: React.FC = ({userXId, screenType}) => { + const {profile: {name = ''} = {}} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + const [drawerVisible, setDrawerVisible] = useState(false); return ( - {!isProfileView && ( + {!userXId && ( <> = ({ }}> - + )} - + {name} diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx index af116dd3..5567fa5a 100644 --- a/src/components/profile/ProfilePreview.tsx +++ b/src/components/profile/ProfilePreview.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useState, useContext} from 'react'; -import {ProfilePreviewType} from '../../types'; +import {ProfilePreviewType, ScreenType} from '../../types'; import { View, Text, @@ -14,8 +14,11 @@ import RNFetchBlob from 'rn-fetch-blob'; import AsyncStorage from '@react-native-community/async-storage'; import {AVATAR_PHOTO_ENDPOINT} from '../../constants'; import {UserType, PreviewType} from '../../types'; -import {AuthContext} from '../../routes/'; import {isUserBlocked} from '../../services'; +import {useSelector, useDispatch, useStore} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {loadUserX, logout} from '../../store/actions'; +import {userXInStore} from '../../utils'; const NO_USER: UserType = { userId: '', username: '', @@ -27,22 +30,24 @@ const NO_USER: UserType = { * If isComment is true then it means that we are not displaying this tile as a part of search results. * And hence we do not cache the search results. * On the other hand, if isComment is false, then we should update the search cache. (This cache needs to be revamped to clear outdated results.) - * In either case, we update the userBeingVisited in our AuthContext (Which can be used to make api calls later on to fetch user specific data). * Finally, We navigate to Profile. */ interface ProfilePreviewProps extends ViewProps { profilePreview: ProfilePreviewType; previewType: PreviewType; + screenType: ScreenType; } const ProfilePreview: React.FC = ({ profilePreview: {username, first_name, last_name, id}, previewType, + screenType, }) => { const navigation = useNavigation(); - const {user: loggedInUser, logout} = React.useContext(AuthContext); + const {user: loggedInUser} = useSelector((state: RootState) => state.user); const [avatarURI, setAvatarURI] = useState(null); const [user, setUser] = useState(NO_USER); + const dispatch = useDispatch(); useEffect(() => { let mounted = true; const loadAvatar = async () => { @@ -87,12 +92,14 @@ const ProfilePreview: React.FC = ({ const checkIfUserIsBlocked = async (userId: string) => { const token = await AsyncStorage.getItem('token'); if (!token) { - logout(); + dispatch(logout()); return false; } return await isUserBlocked(userId, loggedInUser.userId, token); }; + const state: RootState = useStore().getState(); + const addToRecentlyStoredAndNavigateToProfile = async () => { let user: ProfilePreviewType = { id, @@ -145,12 +152,19 @@ const ProfilePreview: React.FC = ({ } /** - * Navigate to profile of the user selected + * Dispatch an event to Fetch the user details + * If the user is already present in store, do not fetch again + * Finally, Navigate to profile of the user selected */ + if (!userXInStore(state, screenType, user.id)) { + dispatch( + loadUserX({userId: user.id, username: user.username}, screenType), + ); + } navigation.push('Profile', { - isProfileView: true, username: user.username, - userId: user.id, + userXId: user.id, + screenType: screenType, }); } catch (e) { console.log(e); -- cgit v1.2.3-70-g09d2