I am fairly new to React Native and I am struggling to get a child view to update/re-render when it should. The code is pasted below. UserProfile contains a tab bar with Posts, Diet, Workouts, and Gallery. UserProfileGalleryView contains an image grid. On click of an image, a modal view should open displaying the image, but currently it will only show once I click an image, tap posts/diet/workout, then tap to go back to Gallery. Once I tap back to gallery, the modal view shows and the close button does not close it. It is evident that my state is not being managed correctly. I have tried using useEffect and useContext to no avail. Any help would be greatly appreciated.
UserProfile (parent component):
import { Auth } from "aws-amplify";
import React, { useEffect } from "react";
import {
View,
SafeAreaView,
Text,
StyleSheet,
Pressable,
Image,
TurboModuleRegistry,
} from "react-native";
import UserProfileWorkoutsView from "./UserProfileWorkoutsView";
import UserProfileDietView from "./UserProfileDietView";
import UserProfileGalleryView from "./UserProfileGalleryView";
import UserProfilePostsView from "./UserProfilePostsView";
function UserProfile({ route, navigation }) {
const [currentView, setCurrentView] = React.useState(UserProfilePostsView);
const userInfo = route.params.userInfo;
const onSettingsPressed = async (data) => {
navigation.navigate("Settings");
};
const [modalVisible, setModalVisible] = React.useState(false);
const [selectedImage, setSelectedImage] = React.useState(null);
const toggleModalView = () => {
setModalVisible(!modalVisible);
};
const updateSelectedImage = (item) => {
setSelectedImage(item);
};
const onPress = (data) => {
switch (data["_targetInst"]["alternate"]["memoizedProps"]["name"]) {
case "postsButton":
setCurrentView(UserProfilePostsView);
return;
case "workoutsButton":
setCurrentView(UserProfileWorkoutsView);
return;
case "dietButton":
setCurrentView(UserProfileDietView);
return;
case "galleryButton":
setCurrentView(() => (
<UserProfileGalleryView
toggleModalView={toggleModalView}
updateSelectedImage={updateSelectedImage}
modalVisible={modalVisible}
selectedImage={selectedImage}
/>
));
return;
}
};
return (
<SafeAreaView
style={styles.container}
contentInsetAdjustmentBehavior="never"
>
<View style={styles.profileHeader}>
<Pressable style={styles.profilePic} onPress={() => {}}>
<Image
style={{ width: 100, height: 100, resizeMode: "contain" }}
source={require("./assets/profile_pic_temp.png")}
/>
</Pressable>
<View>
<Text style={{ fontWeight: "bold" }}>
STREAK {userInfo.userStreak} 🔥
</Text>
<Text style={{ fontWeight: "bold", fontSize: 24 }}>
{userInfo.userFirstLastName}
</Text>
<Text style={{}}>{userInfo.userBio}</Text>
</View>
</View>
<View style={styles.tabBar}>
<Pressable
style={styles.tabBarIcon}
name="postsButton"
onPress={onPress}
>
<Image
style={styles.tabBarIcon}
source={require("./assets/posts_icon.png")}
/>
</Pressable>
<Pressable
style={styles.tabBarIcon}
name="workoutsButton"
onPress={onPress}
>
<Image
style={styles.tabBarIcon}
source={require("./assets/barbell-hand.png")}
/>
</Pressable>
<Pressable
style={styles.tabBarIcon}
name="dietButton"
onPress={onPress}
>
<Image
style={styles.tabBarIcon}
source={require("./assets/diet_icon.png")}
/>
</Pressable>
<Pressable
style={styles.tabBarIcon}
name="galleryButton"
onPress={onPress}
>
<Image
style={styles.tabBarIcon}
source={require("./assets/gallery_icon.png")}
/>
</Pressable>
</View>
<Pressable style={styles.settingsButton} onPress={onSettingsPressed}>
<Image
style={{ width: 40, height: 40, resizeMode: "contain" }}
source={require("./assets/settings_cog.png")}
/>
</Pressable>
<View style={styles.contentContainer}>{currentView}</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "dodgerblue",
alignItems: "center",
justifyContent: "flex-start",
},
settingsButton: {
position: "absolute",
alignSelf: "flex-end",
height: 40,
width: 40,
marginTop: 60,
paddingRight: 60,
},
profileHeader: {
alignItems: "flex-start",
flexDirection: "row",
marginTop: 20,
width: "100%",
marginHorizontal: 80,
},
profilePic: {
alignSelf: "flex-start",
marginRight: 10,
marginLeft: 10,
},
tabBar: {
height: 40,
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
backgroundColor: "lightblue",
alignItems: "center",
marginTop: 20,
},
tabBarIcon: {
flex: 1,
alignSelf: "center",
width: 30,
height: 30,
resizeMode: "contain",
},
contentContainer: {
flex: 1,
width: "100%",
//backgroundColor: "white",
},
});
export default UserProfile;
UserProfileGalleryView (child component):
import React, { useEffect } from "react";
import {
StyleSheet,
Image,
FlatList,
Dimensions,
Modal,
TouchableOpacity,
View,
} from "react-native";
//temp for displaying purposes
const userImages = new Array(17).fill("https://picsum.photos/400");
const screenWidth = Dimensions.get("window").width;
const numColumns = 3;
const tileSize = screenWidth / numColumns;
function renderItem({ item, props }) {
//Single item of Grid
return (
<TouchableOpacity
key={item.id}
style={{ height: tileSize, width: tileSize }}
onPress={() => {
props.toggleModalView();
props.updateSelectedImage(item);
}}
>
<Image resizeMode="cover" style={{ flex: 1 }} source={{ uri: item }} />
</TouchableOpacity>
);
}
function UserProfileGalleryView(props) {
if (props.modalVisible) {
//Modal to show full image with close button
return (
<Modal
transparent={false}
animationType={"fade"}
visible={props.modalVisible}
onRequestClose={() => {
props.toggleModalView();
}}
>
<View style={styles.modelStyle}>
<Image
style={styles.fullImageStyle}
source={{ uri: props.selectedImage }}
/>
<TouchableOpacity
activeOpacity={0.5}
style={styles.closeButtonStyle}
onPress={() => {
props.toggleModalView();
}}
>
<Image
source={{
uri: "https://cdn2.iconfinder.com/data/icons/media-controls-5/100/close-1024.png",
}}
style={{ width: 25, height: 25, marginTop: 16 }}
/>
</TouchableOpacity>
</View>
</Modal>
);
} else {
return (
<FlatList
data={userImages}
renderItem={({ item }) =>
renderItem({
item,
props,
})
}
numColumns={3}
/>
);
}
}
const styles = StyleSheet.create({
photoGrid: {
flex: 3,
marginHorizontal: "auto",
backgroundColor: "dodgerblue",
},
row: {
flexDirection: "row",
},
col: {
backgroundColor: "dodgerblue",
borderColor: "#fff",
borderWidth: 1,
flex: 3,
},
galleryImage: {
width: "100%",
resizeMode: "contain",
},
containerStyle: {
justifyContent: "center",
flex: 1,
marginTop: 20,
},
fullImageStyle: {
justifyContent: "center",
alignItems: "center",
height: "100%",
width: "98%",
resizeMode: "contain",
},
modelStyle: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0,0,0,0.4)",
},
closeButtonStyle: {
width: 25,
height: 25,
top: 50,
right: 20,
position: "absolute",
},
});
export default UserProfileGalleryView;
2
Answers
I agree with the comment above it’s not recommended to store components in state.
That being said I believe the issue you are having is inside of your onPress event when you set the current view you aren’t flipping the modal state as well for your gallery modal. So the view is set but the state value for
modalVisible = false
That is then passed to your child component and the modal isn’t shown.I think this will fix your problem:
Adding the line
setModalVisible(true)
should make the modal be show when you first set the view but allow you to hide it like you currently have setup.To provide a proper answer / explanation for my comment.
You can use the
onPress
function to set a string for thecurrentView
and based on that state you can render you components.