I have an app which displays multiple images which I load from the API. Now the problem is some of the images are expired which is causing a problem on Android, in Android the screen starts lagging as soon as the expired image loads on the screen.
I have tried replacing the image source with onError={() => this.imgRefs[img_unique_id].setNativeProps({src: [{uri: this.state.workingUri}]})} this method but its not working.
I cannot use the local state as it is not saving the output in the local state.
I have tried the following code
<Image
source={image.url}
progressiveRenderingEnabled={true}
ref={item.id}
onError={(e) => this.refs[item.id].setNativeProps({source: [{uri: "working image URL"}]})}
resizeMethod={"scale"}>
</Image>
The above code gives me an undefined setNativeProps error, and if I do not use the onError on android it shows me memory leak error.
Here is a complete example of that. To have own state for every FlatList item, I created a class.
import React, { Component, PureComponent } from 'react';
import { FlatList, Text, View, Image } from 'react-native';
class ItemClass extends PureComponent {
state = {
isImageExit: null
}
componentWillMount = () => {
const { task } = this.props;
Image.getSize(task.url, (width, height) => {
if(width>0){
this.setState({ isImageExit: true });
}
else{
this.setState({ isImageExit: false });
}
}, () => {
this.setState({ isImageExit: false });
});
}
render() {
const { isImageExit } = this.state;
const { task } = this.props;
const url = isImageExit ? task.url : 'https://dummyimage.com/600x400/000/fff';
if (isImageExit === null) {
return null;
}
return (
<Image
style={{ height: 200, width: 200 }}
source={{ uri: url }}
/>
);
}
}
export default class App extends Component {
render() {
const data = [
{ url: 'url' },
{ url:'https://cdn.pixabay.com/photo/2017/08/05/18/53/mountain-2585069_1280.jpg' },
];
return (
<View style={{alignItems: 'center', top: 50}}>
<FlatList
data={data}
renderItem={({ item }) => <ItemClass task={item} />}
/>
</View>
);
}
}
I think you should use data received from Api and set state accordingly inside componentWillReceiveProps because I think setting state is the best way to achieve this result.
.
this.setState = { image: { uri: 'image_uri_from_api' } }
Inside <Image> add -
<Image style={ your_custom_style }
source={ this.state.image }
onError={ this.onError.bind(this) }
/>
Inside onError add this -
onError(error){
this.setState({ image: require('your_local_image.path')})
}
Hope it works now !
Related
Expo BarcodeScanner works only the first time when I post data to my server on barcode scanned
import React, { useState, useEffect } from "react";
import { Text, View, StyleSheet, Button } from "react-native";
import { BarCodeScanner } from "expo-barcode-scanner";
import { useSnapshot } from "valtio";
import phoneState from "../store/phoneState";
import { setqrText, setqrTextFlag } from "../store/phoneState";
export default function ScannerScreen({ navigation }) {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
const [text, setText] = useState("Not yet scanned");
const { qrText, qrTextFlag } = useSnapshot(phoneState);
useEffect(() => {
console.log("qrTexT: ", qrText);
}, [qrText]);
const askForCameraPermission = () => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === "granted");
})();
};
// Request Camera Permission
useEffect(() => {
askForCameraPermission();
}, []);
// What happens when we scan the bar code
const handleBarCodeScanned = ({ type, data }) => {
axios
.post(`http://${localIP}:5000/api/qr/scanQr`, { data })
.then((response) => {
const { message } = response.data;
})
.catch((e) => {
alert("ERR : ", e);
});
};
// Check permissions and return the screens
if (hasPermission === null) {
return (
<View style={styles.container}>
<Text>Requesting for camera permission</Text>
</View>
);
}
if (hasPermission === false) {
return (
<View style={styles.container}>
<Text style={{ margin: 10 }}>No access to camera</Text>
<Button
title={"Allow Camera"}
onPress={() => askForCameraPermission()}
/>
</View>
);
}
// Return the View
return (
<View style={styles.container}>
<View style={styles.barcodebox}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={{ height: 400, width: 400 }}
/>
</View>
<Text style={styles.maintext}>{text}</Text>
{scanned && (
<Button
title={"Go Back"}
onPress={() => {
setScanned(false);
navigation.navigate("Home");
}}
color='tomato'
/>
)}
</View>
);
}
As you can see here, i want to send the scanned data to my server, in order to do that im using axios.post() after barcode scanned. At first try it works just fine, but afterwards it does not scan QR code. I tried to remove my axios.post() code and logged the scanned codes in the terminal, it is scanning all QR codes that i showed the camera as expected. How to change my code to prevent this happenning?
How about toggling some kind of waiting period while the data is actually getting sent to your server via the POST in a synchronous matter to stop the scan and then reactivate it right after?
I don't have the chance to test it on an actual device and replicate it to the precise bit but I hope you'll get the gist of it:
// What happens when we scan the bar code
const handleBarCodeScanned = ({ type, data }) => {
setScanned(true);
(async () => {
try {
const response = await axios.post(`http://${localIP}:5000/api/qr/scanQr`, { data })
const { message } = response.data;
// automatically restoring scanned to keep the ball rolling on the onBarCodeScanned callback
setScanned(false);
} catch (e) {
alert("ERR : ", e);
}
})();
};
The Snack on the doc page also has such kind of behaviour and a warning notice is highlighted on the page itself:
Note: Passing undefined to the onBarCodeScanned prop will result in no
scanning. This can be used to effectively "pause" the scanner so that
it doesn't continually scan even after data has been retrieved.
I have a flatlist showing videos. I want that when a video goes out of the view it should be paused. I am maintaining the pause state in each of the Posts component.
class Posts extends React.PureComponent {
constructor() {
super()
this.state = {
pause: true,
}
return(){
<Video
pause={this.state.pause}
//other props
/>
}
}
I am using react-native-video.
I have tried using onViewableItemsChanged prop of Flatlist but it doesn't change the state.
I tried this .
But it doesn't seem to work for me.
How should I proceed ?
Here is a possible solution using react-native-inviewport. This dependency is only a single index file that contains a component that has a callback when view is in the viewport. It could easily be modified to suit your needs.
I have constructed a very simple app that has a FlatList. The 11th item in the FlatList is a video. That should mean that the video is off the screen when the App renders so the viddeo won't be playing, once the video comes fully into the viewport it should then start playing.
App.js
import * as React from 'react';
import { Text, View, StyleSheet, FlatList } from 'react-native';
import Constants from 'expo-constants';
import VideoPlayer from './VideoPlayer';
export default class App extends React.Component {
state = {
data: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
}
renderItem = ({item, index}) => {
if (index === 10) {
return <VideoPlayer />
} else {
return (
<View style={{height: 100, backgroundColor: '#336699', justifyContent: 'center', alignItems: 'center'}}>
<Text>{index}</Text>
</View>
)
}
}
keyExtractor = (item, index) => `${index}`;
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
}
});
VideoPlayer.js
This is a component that contains the Video component. The video is wrapped in the InViewPort component that has a callback function. The callback returns true when the component it surrounds is completely in the viewport and false when it is not fully in the viewport. The callback calls this.handlePlaying which in turn calls either this.playVideo or this.pauseVideo depending on the boolean value.
import React, {Component} from 'react';
import { View, StyleSheet } from 'react-native';
import { Video } from 'expo-av';
import InViewPort from './InViewPort';
//import InViewPort from 'react-native-inviewport; // this wouldn't work in the snack so I just copied the file and added it manually.
export default class VideoPlayer extends React.Component {
pauseVideo = () => {
if(this.video) {
this.video.pauseAsync();
}
}
playVideo = () => {
if(this.video) {
this.video.playAsync();
}
}
handlePlaying = (isVisible) => {
isVisible ? this.playVideo() : this.pauseVideo();
}
render() {
return (
<View style={styles.container}>
<InViewPort onChange={this.handlePlaying}>
<Video
ref={ref => {this.video = ref}}
source={{ uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4' }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode="cover"
shouldPlay
style={{ width: 300, height: 300 }}
/>
</InViewPort>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center'
}
});
Here is a snack showing it working https://snack.expo.io/#andypandy/video-only-playing-when-in-viewport
I should point out that if the video is not fully in the viewport then it will not play. I am sure some tweaking could be done to react-native-inviewport so that it would play the video if it was partially in the viewport if that is what you wanted, perhaps by passing the height of the video to the InViewPort component.
here is how i simply did the trick
inside my card component that have video
<Video ...
paused={currentIndex !== currentVisibleIndex}
/>
both currentIndex and currentVisibleIndex are passed the component from the FlatList parent
my FlatList pass the renderItem index as currentIndex
<FlatList
data={[...]}
renderItem={({ item, index }) => (
<GalleryCard
{...item}
currentIndex={index}
currentVisibleIndex={currentVisibleIndex}
/>
)}
onViewableItemsChanged={this.onViewableItemsChanged}
viewabilityConfig={{
viewAreaCoveragePercentThreshold: 90
}}
finally my this is how to calculate currentVisibleIndex
please make sure to read viewabilityConfig
onViewableItemsChanged = ({ viewableItems, changed }) => {
if (viewableItems && viewableItems.length > 0) {
this.setState({ currentVisibleIndex: viewableItems[0].index });
}
};
please let me know if this is helpful
I finally did this using redux. Not sure whether it is the right way.
Home.js
_renderItem = ({item, index}) => <Posts item={item} />
viewableItemsChanged = (props) => {
let {changed} = props;
let changedPostArray = [];
changed.map((v,i) => {
let {isViewable, item} = v;
if(!isViewable) {
let {post_id, type} = item;
if(type === 1)
changedPostArray.push(post_id);
}
});
if(changedPostArray.length != 0)
this.props.sendPostToPause(changedPostArray);
}
render() {
return(
<View style={{flex: 1}} >
<FlatList
ref={(ref) => this.homeList = ref}
refreshing={this.state.refreshing}
onRefresh={async () => {
await this.setState({refreshing: true, postsId: [0], radiusId: 0, followingId: 0});
this.getPosts();
}}
onEndReached={async () => {
if(!this.state.isEnd) {
await this.setState({isPaginate: true})
this.getPosts()
}
}}
onEndReachedThreshold={0.2}
removeClippedSubviews={true}
contentContainerStyle={{paddingBottom: 80}}
data={this.state.followersRes}
renderItem={this._renderItem}
viewabilityConfig={this.viewabilityConfig}
onViewableItemsChanged={this.viewableItemsChanged}
/>
</View>
<Footer callback={this.scrollToTopAndRefresh} />
</View>
)
}
export default connect(null, {sendPostToPause})(Home);
Posts.js
class Posts extends React.PureComponent {
constructor(){
super()
this.state: {
pause: false
}
}
componentDidUpdate(prevProps) {
if(prevProps != this.props) {
this.props.postIdArray.map((v) => {
if(v === prevProps.item.post_id) {
this.setState({pause: true})
}
})
}
}
render(){
return(
<Video
pause={this.state.pause}
//Other props
/>
)
}
}
const mapStateToProps = (state) => {
const {postId} = state.reducers;
return {
postIdArray: postId
}
}
export default connect(mapStateToProps, {sendPostToPause})(withNavigation(Posts));
Whenever the viewableItemsChanged is trigger I am adding the changed posts id in an array and calling the action with the array of post ids.
In the Posts component I am checking if the post ids match, if so I am setting the pause state to true.
function cacheImages(images) {
return images.map(image => {
if (typeof image === 'string') {
return Image.prefetch(image);
} else {
return Asset.fromModule(image).downloadAsync();
}
});
}
function cacheFonts(fonts) {
return fonts.map(font => Font.loadAsync(font));
}
export default class App extends React.Component {
constructor (props) {
super(props);
this.state = { isReady : false };
}
async _loadAssetsAsync () {
const imageAssets = cacheImages(require('./assets/icon.png'));//['https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png']);
const fontAssets = cacheFonts([FontAwesome.font]);
await Promise.all([...imageAssets, ...fontAssets]);
}
render() {
if (!this.state.isReady) {
return (
<AppLoading
startAsync={this._loadAssetsAsync}
onFinish={() => this.setState({ isReady: true })}
onError={alert('Error loading assets')}/>
);
}
return (
<Provider store={Store}>
<View style={{ flex:1, width: '100%', height: '100%' }}>
<Navigator></Navigator>
<LoadingModal></LoadingModal>
</View>
</Provider>
);
I tried to use the expo provided code on their website to prefetch the assets and other related images before the application load.
i received an error after rendered the Apploading element. The exception does not show any particular error which make any sense.
AppLoading threw an unexpected error when loading:
cacheImages#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false&assetPlugin=%2FUsers%2Fsimonlam%2FDesktop%2Freact_native%2F
Remove the alert in the AppLoading onError method and use console.error instead,
There's a parsing error on that line.
You can use-
onError={(error)=> console.warn(error)}
this will make your code run
I'm new to React Native and I wanted to implement the camera component from Expo to use it, I followed the tutorial given in the documentation but it didn't work for me. Here is the code I used in my Camera js file:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { Camera, Permissions } from 'expo';
export default class CameraApp extends Component {
state = {
hasCameraPermission: null,
type: Camera.Constants.Type.back,
}
async componentWillMount() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({
hasCameraPermission: status === 'granted'
});
}
render() {
const { hasCameraPermission } = this.state;
if(hasCameraPermission === null) {
return <Text>Hello</Text>;
} else if(hasCameraPermission === false) {
return <Text>No access to camera</Text>;
} else {
return (
<View style={{flex: 1}}>
<Camera type={this.state.type} style={{flex: 1}}/>
</View>
);
}
}
}
and added it to the render method in App.js like this using native-base:
render() {
if(this.state.fontLoaded === true) {
return (
<Container>
<View style={{flex: 1}}>
<HeaderNoLeft />
</View>
<Content>
<View style={{flex: 1}}>
<CameraApp />
</View>
</Content>
</Container>
);
} else {
return <Expo.AppLoading />;
}
}
}
What could be the problem? I can't seem to understand why is the camera preview not showing in the app and I'm stuck to this. Any help is appreciated.
Try giving a height parameter instead of flex, flex didn't work for me either.
<Camera type={this.state.type} style={{height: 300}}/>
It's not a good solution but seeing the problem is about styling is something.
Instead : const { status } = await Permissions.askAsync(Permissions.CAMERA);
Try this:
import * as Permissions from "expo-permissions";
...
const { status } = await Permissions.askAsync(Permissions.CAMERA);
Using Expo SDK 29 for a react native application.
I would like to use a flat list component. This makes up the entirety of a SafeAreaView component. I make this point as there are lots of issues relating to a flat list inside of a scroll view which this is not.
The flat list shows a list of jobs.
I have added a jobLoading boolean to the redux state to manage when the list should show as refreshing and can confirm that this toggles as expected when firing the actions to fetch the data and the success.
When i add the props to the flat list for onRefresh and refreshing the component seems to work by showing the activity indicator in the UI but does not fire the onRefresh function. I have tried implementing the call in numerous ways but nothing happens. The result is that the activity indicator shows itself and never disappears.
As it's Expo SDK 29 the React Native version is 0.55.4
Anyone have any ideas of what to try. I've spent a couple of hours looking at this trying various things but suggestions are welcome.
Thanks in advance.
EDIT: Added the code for reference. Reducer for refreshing sets true when fetchJobs() is dispatched and false when a success or error is recieved. The console log for onRefresh never triggers.
import * as React from 'react'
import * as actions from '../../redux/actions'
import { ActivityIndicator, FlatList, KeyboardAvoidingView, Dimensions, SafeAreaView, StyleSheet, View } from 'react-native'
import { ApplicationState, JobState, Job } from '../../redux'
import { Button, Form, Input, Item, Text, Icon } from 'native-base'
import { JobListItem } from './jobListItem'
import { StateHandlerMap, compose, lifecycle, withPropsOnChange, withStateHandlers } from 'recompose'
import { connect } from 'react-redux'
interface ReduxStateProps {
jobs: JobState
refreshing: boolean
screenOrientation: string
}
interface ReduxDispatchProps {
fetchJobs: (param?: string) => any
}
export interface DataItem {
key: string
data: Job
}
interface ListProps {
jobList: DataItem[]
}
interface SearchStateProps {
timer: number | undefined
searchString: string
}
interface SearchHandlerProps extends StateHandlerMap<SearchStateProps> {
updateSearch: (searchString: string) => any
setTimer: (timer: number | undefined) => any
}
type OuterProps = {}
type InnerProps = OuterProps & ReduxStateProps & ReduxDispatchProps & ListProps & SearchStateProps & SearchHandlerProps
const enhance = compose<InnerProps, OuterProps>(
connect<ReduxStateProps, ReduxDispatchProps, OuterProps, ApplicationState>(
state => ({
jobs: state.job,
refreshing: state.jobLoading,
screenOrientation: state.screenOrientation
}),
dispatch => ({
fetchJobs: (param?: string) => dispatch(actions.jobs.request({ param }))
})
),
withPropsOnChange<ListProps, OuterProps & ReduxStateProps & ReduxDispatchProps>(
['jobs', 'screenOrientation'],
props => ({
jobList: props.jobs && Object.keys(props.jobs).map(job => ({ key: job, data: props.jobs[Number(job)] }))
})
),
withStateHandlers<SearchStateProps, SearchHandlerProps, OuterProps>(
{
timer: undefined,
searchString: ''
},
{
updateSearch: state => (searchString: string) => ({ searchString }),
setTimer: state => (timer: number | undefined) => ({ timer })
}
),
lifecycle<InnerProps, {}>({
componentDidMount() {
this.props.fetchJobs()
}
})
)
export const JobList = enhance(({ fetchJobs, jobList, refreshing, screenOrientation, searchString, setTimer, timer, updateSearch }) => {
const onSearchChange = (search: string) => {
clearTimeout(timer)
updateSearch(search)
const timing = setTimeout(() => {
fetchJobs(search)
}, 500)
setTimer(timing)
}
const onRefresh = () => {
console.log('requesting refresh')
fetchJobs()
}
return (
<SafeAreaView style={{ flex: 1}}>
<KeyboardAvoidingView style={{ flexDirection: 'row', justifyContent: 'space-evenly', paddingTop: 3, paddingRight: 3 }}>
<Form style={{ flex: 1, paddingLeft: 10, paddingRight: 10 }}>
<Item>
<Input
value={searchString}
onChangeText={(text: string) => onSearchChange(text)}
placeholder='Search'
/>
</Item>
</Form>
<Button onPress={() => {fetchJobs(); updateSearch('')}}>
<Icon name='refresh' />
</Button>
</KeyboardAvoidingView>
{refreshing &&
<View style={styles.refreshContainer}>
<Text style={{ paddingBottom: 10 }}>Fetching Data</Text>
<ActivityIndicator />
</View>
}
<FlatList
keyExtractor={item => item.key}
data={jobList}
renderItem={({ item }) =>
<JobListItem
screenOrientation={screenOrientation}
item={item}
/>
}
onRefresh={onRefresh}
refreshing={refreshing}
/>
</SafeAreaView>
)
})
const styles = StyleSheet.create({
refreshContainer: {
height: 60,
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}
})
I'm having the exact same issue and I'm using expo SDK 30. But my case is a little bit different. The onRefresh function is called everytime I pull, however if I scroll down my list, and scroll back up fast, the loading indicator shows up, but my onRefresh function is not called.
My refreshing prop is set on my reducer, and my onRefresh function dispatches an action that fetches data and set refreshing true and false.
Here is my code:
class NoticiasScreen extends Component {
static navigationOptions = {
header: <Header
title='NotÃcias Alego'
leftComponent={<Image source={require('../../../assets/images/play_grande.png')} style={imageStyle} resizeMode='contain'/>}
/>
}
constructor(props) {
super(props);
this.renderItem = this.renderItem.bind(this);
this.keyExtractor = this.keyExtractor.bind(this);
this.renderContent = this.renderContent.bind(this);
this.navigateToNoticias = this.navigateToNoticias.bind(this);
this.carregarMaisNoticias = this.carregarMaisNoticias.bind(this);
this.onRefresh = this.onRefresh.bind(this);
}
componentDidMount() {
this.props.carregarNoticias(this.props.pagina);
}
renderItem({item}) {
return (
<NoticiaListItem noticia={item} abrirNoticia={this.navigateToNoticias} />
);
}
keyExtractor(item) {
return item.id.toString();
}
navigateToNoticias(noticia) {
this.props.navigation.navigate('NoticiasExibir', { id: noticia.id });
}
onRefresh() {
console.log('onRfresh');
this.props.carregarNoticias(1, true);
}
carregarMaisNoticias() {
const { carregarNoticias, pagina } = this.props;
carregarNoticias(pagina + 1);
}
renderContent() {
const { noticias, carregandoNoticias, erroNoticias } = this.props;
if(noticias.length === 0 && carregandoNoticias) {
return (
<View style={styles.containerCenter}>
<ActivityIndicator size="large" color={colors.verde}/>
</View>
);
}
if(erroNoticias) {
return (
<View style={styles.containerCenter}>
<Text style={styles.message}>{erroNoticias}</Text>
<TouchableOpacity hitSlop={hitSlop15}>
<Text>Recarregar</Text>
</TouchableOpacity>
</View>
)
}
return (
[<TextInput
style={styles.textInput}
placeholder='Pesquise'
key='pesquisa'
underlineColorAndroid='transparent'
/>,
<FlatList
data={noticias}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
style={styles.list}
key='lista'
onRefresh={this.onRefresh}
refreshing={carregandoNoticias}
onEndReached={this.carregarMaisNoticias}
onEndReachedThreshold={0.1}
/>]
)
}
render() {
return (
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
{this.renderContent()}
</View>
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return {
noticias: state.intranet.noticias,
pagina: state.intranet.pagina,
erroNoticias: state.intranet.erroNoticias,
carregandoNoticias: state.intranet.carregandoNoticias
}
}
function mapDispatchToProps(dispatch) {
return {
carregarNoticias: (pagina, recarregar) => dispatch(ActionCreator.carregarNoticias(pagina, recarregar))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NoticiasScreen);
No idea what's going on. Any help is appreciated.
EDIT:
I fixed it somehow. I added the onMomentScrollBegin prop to prevent my flatList from rendering twice on Render, and that fixed this issue.
here is what I added:
constructor(props) {
super(props);
...
this.onRefresh = this.onRefresh.bind(this);
this.onMomentumScrollBegin = this.onMomentumScrollBegin.bind(this);
this.onEndReachedCalledDuringMomentum = true; //PUT THIS HERE
}
onRefresh() {
this.props.carregarNoticias(1, true);
}
carregarMaisNoticias() {
if(!this.onEndReachedCalledDuringMomentum){
const { carregarNoticias, pagina } = this.props;
carregarNoticias(pagina + 1);
this.onEndReachedCalledDuringMomentum = true;
}
}
onMomentumScrollBegin() {
this.onEndReachedCalledDuringMomentum = false;
}
render() {
<OptimizedFlatList
data={noticias}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
style={styles.list}
key='lista'
onRefresh={this.onRefresh}
refreshing={carregandoNoticias}
onMomentumScrollBegin={this.onMomentumScrollBegin} //PUT THIS HERE
onEndReached={this.carregarMaisNoticias}
onEndReachedThreshold={0.1}
/>
}