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
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 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
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 have custom markers being rendered to my map and do so exactly as I want. I'm using Custom Marker which is a View, Image, and then text specific to an event over the image.
For some reason using a custom marker view is blocking the onPress on the Marker component.
I've tested the onPress event as similiar to below for testing:
<Marker onPress={() => {console.log('Pressed')}}> ... but only logs the press message when I remove my custom marker view inside the Marker component.
Is there a reason why the custom marker view would be stopping the onPress? How do I get around this and fire an onPress event for a custom marker?
Added full code below for better look. Right now it is working but only because I am using the onSelect event for iOS. The onPress works the way it is for Android but not iOS.
<MapView
style={styles.map}
ref={ map => {this.map = map }}
region={this.state.region}
onRegionChangeComplete={this.onRegionChange}
rotateEnabled={false}
loadingEnabled={true}
>
{matches.map((marker, index) => {
return (
<MapMarker
key={index}
mapMarker={marker}
handlePress={() => {
this.map.animateToRegion({
latitude: marker.location.latlng.latitude,
longitude: marker.location.latlng.longitude
}, 100)
}}
/>
)
})}
</MapView>
MapMarker.js
render() {
const { mapMarker } = this.props;
return (
<Marker
ref={(ref) => {this.markerRef = ref; }}
coordinate={mapMarker.location.latlng}
title={mapMarker.location.streetName}
stopPropagation={true}
pointerEvents='auto'
onPress={() => console.log('pressed')}
onSelect={() => {
this.props.handlePress();
}}
>
<CustomMapMarker
gameType={mapMarker.matchType}
isSanctioned={mapMarker.isSanctioned}
startDate={mapMarker.startAt}
/>
<Callout style={styles.customCallout}>
<CustomMarkerCallout markerInfo={mapMarker} />
</Callout>
</Marker>
);
}
Fixed the issue by adding onSelect which is iOS specific. The onPress was only working for Android so don't know if this is the right answer but is working for me as of now.