I am new to React Native and am not completely familiar with state management. Currently I am using react-native-swiper library to hold my swipable images. Each swipe takes me to the next image. Code below
Fish.js:
<Swiper style={styles.wrapper} height={10} horizontal={true} >
<View style={styles.slide1}>
<Image
resizeMode="stretch"
style={styles.image}
source={require('../resource/images/AzureDamsel.png')}
/>
</View>
<View style={styles.slide2}>
<Image
resizeMode="stretch"
style={styles.image}
source={require('../resource/images/BicolourAngelfish.png')}
/>
</View>
<View style={styles.slide3}>
<Image
resizeMode="stretch"
style={styles.image}
source={require('../resource/images/ClownTriggerfish.png')}
/>
</View>
</Swiper>
And I am importing this in my App.js where I have some Text component below this like so:
<View style={{ flex: 1 }}>
<ImageBackground
style={globalStyles.backgroundImage}
source={require('./resource/images/bg1080.jpeg')}
>
<Fish />
<Text> Fish 1</Text>
<Text> Fish 2</Text>
</ImageBackground>
</View>
But I want the respective Text to show with each swipe. Example at image 1 the Text says Fish 1, then when I swipe the image and go to the second slide, the Text changes to Fish 2 and so on. Any help would be appreciated.
You can maintain the active slide index in state and then render that index.
Inside of App.js
const [activeIndex, setActiveIndex] = useState();
const onIndexChanged = (index) => setActiveIndex(index);
return (
<View>
<Fish
onIndexChanged={onIndexChanged}
/>
{ activeIndex && <Text>`Fish ${activeIndex}`</Text> }
</View>
)
Inside of Fish.js you need to pass the onIndexChanged prop into Swiper.
const Fish = ({ onIndexChanged }) => (
<Swiper onIndexChange={onIndexChanged}>
// Rest of your code.
</Swiper>
)
Checking your Snack, I can see some output but as you mentioned it's not what you are expecting.
Start the activeIndex state variable to 0.
const [activeIndex, setActiveIndex] = useState(0);
Pass setActiveIndex straight into Fish via the onIndexChanged prop. Now you will be able to remove the unnecessary onIndexChanged function from App.js
<Fish onIndexChanged={setActiveIndex} />
Change your string interpolation to just render the text from fishMapping. Because activeIndex is no longer undefined, you do not need the additional check (activeIndex &&)
<Text>{fishMapping[activeIndex]}</Text>
Here's an updated version of your Snack.
I went through the snack provided by you and the issue is in the Fish.js file.
Replace onIndexChange with onIndexChanged. The updated code:
const Fish = ({ onIndexChanged }) => (
<Swiper onIndexChanged={onIndexChanged}>
// Rest of your code.
</Swiper>
)
Link to updated snack : https://snack.expo.dev/BToSGo1z6
Related
I'm struggling to understand what causes my component to rerender as there is no state present here. Code to focus on is Flatlist as this is React Native but this is about React and not specifically RN. As you can see inside Flatlist I render renderPost. I know that renderPost, aka reusable component PostListItem rerenders because this console.log you can see inside flatlist gives me repeated keys, and even console logging postsSortedByDate shows me that they render few times. Because of this, I even have a warning child list keys must be unique while I definitely gave flatlist unique IDs from my array, that warning is because of these rerendering. I guess I need to use Memo somewhere?:
const FeedScreen: React.FC<FeedScreenProps> = ({ navigation }) => {
const { postsSortedByDate } = checkPostsContext();
const navigateToCreatePostScreen = async () => {
navigation.navigate("CreatePostScreen");
};
const renderPost: ListRenderItem<IPost> = ({ item }) => (
<PostListItem post={item} key={item.uniquePostID} />
);
return (
<ScreenContainer>
<SafeAreaView>
<Header
containerStyle={styles.containerStyleHeader}
headerTitle="Factory X Feed"
rightIcon="add-photo-alternate"
onPressRightIcon={navigateToCreatePostScreen}
/>
</SafeAreaView>
{postsSortedByDate.length === 0 ? (
<View style={styles.noPostsContainer}>
<Text>
No posts exist at the moment, please create one by tapping on the
image icon in the header. In the meantime, as indicated by the
spinner below, we will continue to try to fetch posts in case some
do exist but are just loading slowly.
</Text>
<ActivityIndicator size="large" color="#0000ff" />
</View>
) : (
<FlatList
data={postsSortedByDate}
renderItem={renderPost}
keyExtractor={(item, index) => {
console.log("IDS:", item.uniquePostID.toString());
//HERE IS THE ISSUE as here I see same keys rendering //multiple times, and I checked and I did not save posts in a wrong way //for few of them to have the same key somehow. Its now about that. //Even logging posts themselves(here) shows me they re render.
return item.uniquePostID.toString();
}}
/>
)}
</ScreenContainer>
);
};
My PostListItem component:
const PostListItem: React.FC<PostItemProps> = ({ post }) => {
return (
<View style={styles.container}>
{post.postImage ? (
<Lightbox
renderContent={() => {
return (
<Image
source={{ uri: post.postImage }}
resizeMode="cover"
style={[
styles.imageInLightbox,
{
width: width,
height: height,
},
]}
/>
);
}}
>
<Image
source={{ uri: post.postImage }}
resizeMode="contain"
style={styles.image}
/>
</Lightbox>
) : null}
<React.Fragment>
{post.postDescription ? (
<Text style={styles.textStyle}>
{hashtagFormatter(post.postDescription)}
</Text>
) : (
<Text style={styles.textStyle}>{defaultPostDescription}</Text>
)}
</React.Fragment>
</View>
);
So why is PostListItem, I mean renderPost rerendering and how to fix it?
I am using https://gorhom.github.io/react-native-bottom-sheet/.
I was wondering how can I open "BottomSheetModal" in a different file e.g Navbar Component.
This is what my code looks like at the moment to open the Bottom Sheet inside of the same component.
const BottomSheetModal: FC = () => {
const bottomSheetModalRef = useRef<BottomSheet>(null);
const snapPoints = useMemo(() => ["25%", "50%"], []);
const handlePresentModalPress = useCallback(() => {
bottomSheetModalRef.current?.present();
}, []);
return (
<>
<Button title="Test" onPress={() => handlePresentModalPress()} />
<BottomSheet
index={1}
style={{ ...shadows.bottomSheet }}
ref={bottomSheetModalRef}
snapPoints={snapPoints}>
<View style={styles.container}>
<Text>Awesome 🎉</Text>
</View>
</BottomSheet>
</>
);
};
So how can I use the opening code of the Bottom Sheet inside of my Navbar Component?
Navbar Component:
// Open BottomSheet here
<TouchableWithoutFeedback onPress={() => openBottomSheet()}>
<View>
<Image
style={styles.avatar}
source={{
uri: "https://lumiere-a.akamaihd.net/v1/images/character_themuppets_kermit_b77a431b.jpeg?region=0%2C0%2C450%2C450",
}}
/>
</View>
</TouchableWithoutFeedback>
Thank you!
I found out to do this, incase anyone comes across this question, I'll post it here!
So what you have to do is pass the ref to the bottom sheet component. So in the Navbar component I created the ref for the bottom sheet, and then passed it into the bottom sheet.
Navbar:
// Create Ref
const userBottomSheetRef = useRef<BottomSheetModal>(null);
// Pass ref into the bottom sheet component
<BottomSheet ref={userBottomSheetRef} snapPoints={["30%"]}/>
Then inside the bottom sheet component you forward the ref using a react function, and then pass it in as normal:
<BottomSheetModal ref={ref} >
<BottomSheetScrollView>
<View style={styles.container}>{children}</View>
</BottomSheetScrollView>
</BottomSheetModal>
I am trying to make an app that shows cat pictures on Expo. I have a cat picture that is displayed originally. I have a button and when I click the button, I want to change the cat picture that is displayed. Here is what I have:
let imageUri = {uri: 'https://images.unsplash.com/photo-1615789591457-74a63395c990?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8ZG9tZXN0aWMlMjBjYXR8ZW58MHx8MHx8&w=1000&q=80'};
I define a function that changes the imageUri value:
const displayImage = async () => {
imageUri = {uri: 'https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8Y3V0ZSUyMGNhdHxlbnwwfHwwfHw%3D&w=1000&q=80'}
}
Then inside of return() I do the following:
<View style={styles.container}>
<ImageBackground
source={require('./assets/images/background.png')}
style={styles.image}
/>
<Text style={styles.text}> Here is a cat picture </Text>
<Button
style = {styles.button}
title="Display"
onPress = {() => displayImage()}
/>
<Image source={imageUri} style = {{width: 155, height: 259}} />
</View>
However, it does not update the picture.
The reason your image is not changing is becuase the state of the component is not changing. To solve this you need to manage the state of the component data. You can do this with the hook useState.
Make sure to import useState hook.
import {useState} from 'react';
Then you can set a state managed variable for your image.
const [imageUri, setImageUri] = useState(
'https://images.unsplash.com/photo-1615789591457-74a63395c990?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8ZG9tZXN0aWMlMjBjYXR8ZW58MHx8MHx8&w=1000&q=80'
);
Then all you need is to update your button press event to call the state hook to set the new image into state. So when the image changes your components state updates with the new image.
<View style={styles.container}>
<ImageBackground
source={require('./assets/images/background.png')}
style={styles.image}
/>
<Text style={styles.text}> Here is a cat picture </Text>
<Button
style = {styles.button}
title="Display"
onPress = {() => setImageUri('https://images.unsplash.com/photo-1529778873920-4da4926a72c2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8Y3V0ZSUyMGNhdHxlbnwwfHwwfHw%3D&w=1000&q=80')}
/>
<Image source={imageUri} style = {{width: 155, height: 259}} />
</View>
I have a list using the ScrollView of react native itself. Basically, I build a list dynamically through an API return.
async fetchData(userSearch) {
const {route} = this.props;
const {params} = route;
const {type} = params;
this.setState({
loading: true,
});
const responseProcedures = await scheduleResource.getProcedures(userSearch);
this.setState({
procedures: responseProcedures.data,
loading: false,
});
}
<ScrollView
onScroll={(event) => this.shouldLoadMoreContent(event)}
>
{procedures.map(procedure => (
<ArrowBox key={procedure.id} onPress={() => RootNavigation.navigate('ProcedureDetails', {procedure})}>
<Text bold style={styles.ProcedureTitle}>
{procedure.name}
</Text>
{!!procedure.synonyms.length && (
<>
<Text bold style={styles.ProcedureSynonymTitle}>
Sinônimos
</Text>
<View style={styles.ProcedureSynonymOptionsContainer}>
{procedure.synonyms.map(synonym => <Text style={styles.ProcedureSynonymOption} key={synonym}>{synonym}</Text>)}
</View>
</>
)}
</ArrowBox>
))}
</ScrollView>
The problem is that I load the entire return from the API and it slows down.
I would like to know how to dynamically load the content and make new calls in the api, when I reach the end of the page.
Api allows me to place offset and limit.
Could someone give me some example?
Thanks!!!!!
Basically the ScrollView is not designed to handle dynamic data, the correct component that is designed to perform this function is called Flatlist. It works almost exactly like ScrollView but it is faster and will only render components that are shown on the screen.
Please import Flatlist from React Native like this...
//At the top of your file, please import FlatList together with all the modules that you want
import { FlatList, Text, View } from "react-native";
Then replace the entire ScrollView in your code with a Flatlist like this:
<FlatList
keyExtractor={(procedure) => procedure.id}
data={this.state.procedures}
renderItem={(procedure) => {
return (
<ArrowBox
key={procedure.id}
onPress={() =>
RootNavigation.navigate("ProcedureDetails", {
procedure })}>
<Text bold style={styles.ProcedureTitle}>
{procedure.name}
</Text>
{!!procedure.synonyms.length && (
<>
<Text bold style={styles.ProcedureSynonymTitle}>
Sinônimos
</Text>
<View
style={styles.ProcedureSynonymOptionsContainer}>
{procedure.synonyms.map((synonym) => (
<Text
style={styles.ProcedureSynonymOption}
key={synonym}>
{synonym}
</Text>
))}
</View>
</>
)}
</ArrowBox>
);
}}
></FlatList>;
I'm using react-native-element for ready-made components in my application and using Overlay to show modals through out the application.
Now the issue is I've a Modal in which I want to show Webview but it doesn't showup on the screen.
My modal code looks like this
export const Modal = ({ visible, showCrossBtn, setModalVisible, backgroundColor, children }) => {
return (
<Overlay
isVisible={visible}
fullScreen={false}
height="auto"
overlayBackgroundColor={backgroundColor}
overlayStyle={styles.overlay}
onBackdropPress={() => setModalVisible(false)}>
<View style={styles.dialogBox}>
{
showCrossBtn && <TouchableOpacity style={[styles.crossPosition, styles.crossStyling]} onPress={() => setModalVisible(false)}>
<Icon name="cross" type="entypo" color={colors.gray} size={30} />
</TouchableOpacity>
}
<View style={styles.body}>
{children}
</View>
</View>
</Overlay>
)
}
In my basic component
export const Home = ({ }) => {
return (
....
....
<Modal
visible={this.props.visible}
setModalVisible={this.props.setModalVisible}
backgroundColor={colors.lightgray}
showCrossBtn={false}
>
<WebView
style={[{ height: 20 }, styles.webView]}
originWhitelist={['*']}
ref={e => webview = e}
source={{ html: '<p>HELLO WORLD</p>'}} />
</Modal>
)
}
Using WebView in modals can be tricky due to the height and width.
To solve the issue, I've created a Snack using react-native-elements Overlay component and WebView. I used some of your code and remove any redundant code for brevity.
Use iOS or Android simulator to run the snack. You can also use your physical device to run the snack using expo app.
Snack - https://snack.expo.io/r1H4FSHML