How to limit the quantity of View inside of a scrollview.
My component take too much time to render, because the map function renders too many views. I need to show only 10 views, and when scroll up, renders more 10.
I'm using react native, hooks and typescript.
First of all, if you have a large number of list data don't use scrollview. Because initially, it loads all the data to scrollview component & it costs performance as well.
Use flatlist in react-native instead of scrollview & you can limit the number of items to render in the initially using initialNumToRender. When you reach the end of the scroll position you can call onEndReached method to load more data.
A sample will like this
import React, { Component } from "react";
import { View, Text, FlatList, ActivityIndicator } from "react-native";
import { List, ListItem, SearchBar } from "react-native-elements";
class FlatListDemo extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const { page, seed } = this.state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
this.setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: page === 1 ? res.results : [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({ error, loading: false });
});
};
handleRefresh = () => {
this.setState(
{
page: 1,
seed: this.state.seed + 1,
refreshing: true
},
() => {
this.makeRemoteRequest();
}
);
};
handleLoadMore = () => {
this.setState(
{
page: this.state.page + 1
},
() => {
this.makeRemoteRequest();
}
);
};
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "#CED0CE",
marginLeft: "14%"
}}
/>
);
};
renderHeader = () => {
return <SearchBar placeholder="Type Here..." lightTheme round />;
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
render() {
return (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
keyExtractor={item => item.email}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
onRefresh={this.handleRefresh}
refreshing={this.state.refreshing}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={50}
/>
</List>
);
}
}
export default FlatListDemo;
Check this for more informations.
I changed to Flatlist! But initialNumToRender is not working as expected.
The flatlist is rendering all 150 transactions, not only 15, and i have no idea what to do.
I'm running .map() from another array with all others transactions to create newMonths with only those transactions that i want to use data={newMonths}.
let newMonths = [];
const createArrayMonth = histInfos.map(function (info) {
if (info.created_at.slice(5, 7) === month) {
newMonths = [info].concat(newMonths);
}
});
them, i created my component:
function Item({ value }: { value: any }) {
let createdDay = value.item.created_at.slice(8, 10);
let createdMonth = value.item.created_at.slice(5, 7);
let createdYear = value.item.created_at.slice(2, 4);
function dateSelected() {
if (
month === createdMonth &&
year === createdYear &&
(day === '00' || day == createdDay)
) {
console.log('foi dateSelected');
const [on, setOn] = useState(false);
const Details = (on: any) => {
if (on === true) {
return (
<View style={styles.infos}>
<Text style={styles.TextInfos}>
{' '}
CPF/CNPJ: {value.item.cpf_cnpj}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Criado em: {value.item.created_at}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Endereço da carteira: {value.item.address}{' '}
</Text>
<Text style={styles.TextInfos}> Valor: {value.item.amount}BTC </Text>
</View>
);
} else {
return <View />;
}
};
return (
<View>
<TouchableOpacity
style={styles.card}
onPress={() => setOn(oldState => !oldState)}>
<View style={styles.dateStyleView}>
<Text style={styles.dateStyle}>{createdDay}</Text>
</View>
<View style={styles.left}>
<Text style={styles.title}>Venda rápida</Text>
<Text style={styles.semiTitle}>
{
{
0: 'Pendente',
1: 'Aguardando conclusão',
2: 'Cancelado',
100: 'Completo',
}[value.item.status]
}
</Text>
</View>
<View style={styles.right2}>
<Text style={styles.price}>R$ {value.item.requested_value}</Text>
</View>
</TouchableOpacity>
<View>{Details(on)}</View>
</View>
);
}
}
return dateSelected();}
and i call it here
return (
<ScrollView>
...
<View style={styles.center}>
...
<View style={styles.middle2}>
...
<FlatList
extraData={[refresh, newMonths]}
data={newMonths}
renderItem={(item: any) => <Item value={item} />}
keyExtractor={(item, index) => index.toString()}
initialNumToRender={15}
/>
</View>
</View>
</ScrollView>);}
The scroll bar in the right, start to increase until renders all transactions from the data:
App scroll bar
Related
I have a simple react native app with two screens.
First screen is the list, where you see your selected groups, and you can remove them, by clicking on trash icon:
export const Settings: NavioScreen = observer(({ }) => {
...
return (
<View flex bg-bgColor padding-20>
<FlashList
contentInsetAdjustmentBehavior="always"
data={toJS(ui.savedGroups)}
renderItem={({item}) => <ListItem item={item} />}
estimatedItemSize={20}
/>
</View>
);
});
};
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<TouchableOpacity onPress={() => ui.deleteGroup(item)}>
<Icon name={'trash'}/>
</TouchableOpacity>
</View>
);
};
The second screen is also the list, where you can add and remove the subjects from the list:
export const Playground: NavioScreen = observer(() => {
...
const groupsToShow =
ui.search && ui.search.length > 0
? ui.groups.filter((p) =>
p.name.toLowerCase().includes(ui.search.toLowerCase())
)
: ui.groups;
return (
<View >
<FlashList
data={toJS(groupsToShow)}
renderItem={({item}) => <ListItem item={item} />}
/>
</View>
);
});
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<View>
<If
_={ui.isGroupSaved(item)}
_then={
<TouchableOpacity onPress={(e) => {ui.deleteGroup(item)}}>
<AntDesign name="heart" size={20} color={Colors.primary} />
</TouchableOpacity>
}
_else={
<TouchableOpacity onPress={(e) => {ui.addGroup(item)}}>
<AntDesign name="hearto" size={20} color={Colors.primary} />
</TouchableOpacity>
}
/>
</View>
</View>
);
};
And now when I remove the group from the first list, the heart icon do not update on the second list. But it should, because there is an if statement on second list, that checks if the group is saved. And if it is not, the heart should have the name="hearto"
I have tried to use the state instead mobx library but it does not also help.
Here is my store written with mobx:
export class UIStore implements IStore {
savedGroups = [];
constructor() {
makeAutoObservable(this);
makePersistable(this, {
name: UIStore.name,
properties: ['savedGroups'],
});
}
addGroup = (group: any) => {
if (true === this.isGroupSaved(group)) {
return;
}
this.savedGroups.push(group);
}
isGroupSaved = (group: any) => {
return -1 !== this.savedGroups.findIndex(g => g.id === group.id);
}
deleteGroup = (groupToDelete: any) => {
this.savedGroups = this.savedGroups.filter((group) => group.id !== groupToDelete.id);
}
}
I am using the infinite scrolling Flatlist pattern of react-native mentioned on the internet. So far it's good for having long data. But when the rest API pagination completed, it returns the empty JSON array. Now with infinite scrolling pattern, onEndReached function is infinitely thrown leading to unnecessary rest call. So what's the best way to deal with this situation.
The data from API could be a single row or 500 rows of JSON object depending upon the conditional URL mentioned in code. In the case of 500 rows, it's fine till there's more data to fetch but after the last 480-500 batch, it becomes an infinite loop, which is a problem. In the case of 1 row, it immediately becomes an endless loop. How could I break the onEndReached event conditionally so that it never trigger when I detect empty array from rest api.
Below is my Flatlist implementation:
import React, { Component } from 'react';
import { StyleSheet, Text, View, FlatList, Image, ActivityIndicator, TouchableOpacity, ToastAndroid } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import GLOBAL from './productglobal'
import { Ionicons } from '#expo/vector-icons';
import SearchBar from '../commoncomponents/searchbar'
export default class ProductsList extends Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: 'Products',
headerRight: () => (
<TouchableOpacity
style={{ paddingRight: 20 }}
onPress={() => { navigation.navigate("Settings") }}
>
<Ionicons name='md-more' size={25} color='white' />
</TouchableOpacity>
),
}
};
constructor(props) {
super(props);
this.state = {
loading: false,
searchValue: '',
data: [],
page: 1,
error: null,
refreshing: false,
base_url: null,
c_key: null,
c_secret: null,
};
GLOBAL.productlistScreen = this;
}
async componentDidMount() {
await this.getCredentials();
this.fetchProductList();
}
getCredentials = async () => {
const credentials = await SecureStore.getItemAsync('credentials');
const credentialsJson = JSON.parse(credentials)
this.setState({
base_url: credentialsJson.base_url,
c_key: credentialsJson.c_key,
c_secret: credentialsJson.c_secret,
})
}
fetchProductList = () => {
const { base_url, c_key, c_secret, page, searchValue } = this.state;
let url = null
if (searchValue) {
url = `${base_url}/wp-json/wc/v3/products?per_page=20&search=${searchValue}&page=${page}&consumer_key=${c_key}&consumer_secret=${c_secret}`;
} else {
url = `${base_url}/wp-json/wc/v3/products?per_page=20&page=${page}&consumer_key=${c_key}&consumer_secret=${c_secret}`;
}
console.log(url)
this.setState({ loading: true });
setTimeout(() => {
fetch(url).then((response) => response.json())
.then((responseJson) => {
this.setState({
data: this.state.data.concat(responseJson),
error: responseJson.code || null,
loading: false,
refreshing: false
});
}).catch((error) => {
this.setState({
error,
loading: false,
refreshing: false
})
});
}, 1500);
};
renderListSeparator = () => {
return (
<View style={{
height: 1,
width: '100%',
backgroundColor: '#999999'
}} />
)
}
renderListFooter = () => {
if (!this.state.loading) return null;
return (
<View style={{
paddingVertical: 20,
}}>
<ActivityIndicator color='#96588a' size='large' />
</View>
)
}
handleRefresh = () => {
this.setState({
page: 1,
refreshing: true,
data: []
}, () => {
this.fetchProductList();
}
)
}
handleLoadMore = () => {
console.log('loading triggered')
this.setState({
page: this.state.page + 1,
}, () => {
this.fetchProductList();
})
}
handleSearch = (value) => {
// console.log(value)
this.setState({
searchValue: value,
page: 1,
refreshing: true,
data: []
}, () => {
this.fetchProductList()
})
}
render() {
return (
<View style={{flex:1}}>
<SearchBar onSearchPress={this.handleSearch}></SearchBar>
<FlatList
data={this.state.data}
keyExtractor={item => item.id.toString()}
refreshing={this.state.refreshing}
extraData={this.state.data}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={0.5}
ItemSeparatorComponent={this.renderListSeparator}
ListFooterComponent={this.renderListFooter}
renderItem={({ item }) =>
<TouchableOpacity onPress={() => {
this.props.navigation.navigate('ProductDetails', {
productId: item.id,
productName: item.name,
base_url: this.state.base_url,
c_key: this.state.c_key,
c_secret: this.state.c_secret
});
}}>
<View style={{ flex: 1, flexDirection: 'row', backgroundColor: 'white' }}>
<View style={{ flex: 1, justifyContent: "center", alignContent: "center" }}>
<Image source={(Array.isArray(item.images) && item.images.length) ?
{ uri: item.images[0].src } :
require('../../../assets/images/blank_product.png')}
onError={(e) => { this.props.source = require('../../../assets/images/blank_product.png') }}
style={{ height: 115, width: 115 }} resizeMode='contain' />
</View>
<View style={{ flex: 2, marginTop: 10, marginBottom: 10, justifyContent: "center" }}>
<View style={{ marginLeft: 10 }}>
<Text style={styles.titleText}>{item.name}</Text>
<Text>SKU: {item.sku}</Text>
<Text>Price: {item.price}</Text>
<Text>Stock Status: {item.stock_status}</Text>
<Text>Stock: {item.stock_quantity}</Text>
<Text>Status: {item.status}</Text>
</View>
</View>
</View>
</TouchableOpacity>
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
titleText: {
fontSize: 20,
fontWeight: 'bold',
}
});
You can add a state property hasMoreToLoad which defaults to true.
Then you can check in fetchProductList if the result is less than per_page (data < 20) IF the result is less than per_page you know you've reached the end and you can set hasMoreToLoad to false.
onEndReached={this.state.hasMoreToLoad ? this.handleLoadMore : null}
Just simply put condition on onEndReached() :
<FlatList
data={this.state.data}
keyExtractor={item => item.id.toString()}
refreshing={this.state.refreshing}
extraData={this.state.data}
onRefresh={this.handleRefresh}
onEndReached={this.status.data.length > 0 ? this.handleLoadMore : null} // change here
...
...
/>
I'm fetching api(makeup API) in Explore component and using it also in Explorebutton.
Im taking brands as a button in ExploreButtons. When i click button in FlatList element in ExploreButtons I want to see images from api in second FlatList in ExploreButtons. Is there a way componentDidMount can rerender when i click button?
import React, { Component } from 'react'
import { View } from 'react-native'
import ExploreButtons from './ExploreButtons'
export default class Explore extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
makeupApi: 'http://makeup-api.herokuapp.com/api/v1/products.json',
}
}
callbackFunction = (item) => {
this.setState({
makeupApi: 'http://makeup-api.herokuapp.com/api/v1/products.json?brand=' + item,
})
}
async componentDidMount() {
try {
const response = await fetch(this.state.makeupApi);
const responseJson = await response.json();
this.setState({
isLoading: false,
dataSource: responseJson,
}, function () {
});
const reformattedArray = this.state.dataSource.map(obj => {
var rObj = {};
rObj = obj.brand;
return rObj;
});
this.setState({
duplicatesRemoved: reformattedArray.filter((item, index) => reformattedArray.indexOf(item) === index)
})
}
catch (error) {
console.error(error);
}
};
render() {
console.log(this.state.makeupApi)
return (
<View style={{ flex: 1 }}>
<ExploreButtons
api={this.state.dataSource}
removedDuplicatesFromAPI={this.state.duplicatesRemoved}
parentCallback={this.callbackFunction}
makeupApi= {this.state.makeupApi} />
</View>
)
}
}
export default class ExploreButtons extends Component {
getBrandImages = (item) => {
this.props.parentCallback(item)
}
render() {
return (
<View style={{ flex: 1 }}>
<View>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={this.props.removedDuplicatesFromAPI}
renderItem={({ item }) =>
<TouchableOpacity
style={styles.exploreButtons}
onPress={() => {
this.getBrandImages(item)
}}
>
<Text>{item}</Text>
</TouchableOpacity>
}
keyExtractor={item => item}
/>
</View>
<View>
<FlatList
data={this.props.api}
renderItem={({ item }) =>
<View>
<Image source={{ uri: item.image_link }}
style={{
alignSelf: "center",
width: '100%',
height: 300,
}} />
</View>
}
keyExtractor={item => item.id.toString()} />
</View>
</View>
)
}
}
You could just put all the logic inside componentDidMount on another function and call it when you call the callback. As a first very rough approach this would work:
Notes: you don't really need the API URL in the state, put the item on the state and construct the URL based on it.
import React, { Component } from 'react';
import { View } from 'react-native';
import ExploreButtons from './ExploreButtons';
export default class Explore extends Component {
API_URL = 'http://makeup-api.herokuapp.com/api/v1/products.json';
constructor(props) {
super(props);
this.state = {
isLoading: true,
item: null,
dataSource: null,
duplicatesRemoved: [],
};
}
getAPIURL(item) {
if(!item){
return API_URL
}
return `${API_URL}?brand=${item}`;
}
async fetchData(item) {
try {
const url = getAPIURL(item);
const response = await fetch(url);
const responseJson = await response.json();
this.setState({
isLoading: false,
dataSource: responseJson,
item,
});
const reformattedArray = responseJSON.map(({ brand }) => brand);
this.setState({
duplicatesRemoved: reformattedArray.filter(
(item, index) => reformattedArray.indexOf(item) === index,
),
});
} catch (error) {
console.error(error);
}
}
async componentDidMount() {
fetchData();
}
render() {
const { dataSource, duplicatesRemoved, item } = this.state;
return (
<View style={{ flex: 1 }}>
<ExploreButtons
api={dataSource}
removedDuplicatesFromAPI={duplicatesRemoved}
parentCallback={this.fetchData}
makeupApi={getURL(item)}
/>
</View>
);
}
}
export default class ExploreButtons extends Component {
getBrandImages = item => {
this.props.parentCallback(item);
};
render() {
const { removedDuplicatesFromAPI, api } = this.props;
return (
<View style={{ flex: 1 }}>
<View>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={removedDuplicatesFromAPI}
renderItem={({ item }) => (
<TouchableOpacity
style={styles.exploreButtons}
onPress={() => {
this.getBrandImages(item);
}}
>
<Text>{item}</Text>
</TouchableOpacity>
)}
keyExtractor={item => item}
/>
</View>
<View>
<FlatList
data={api}
renderItem={({ item }) => (
<View>
<Image
source={{ uri: item.image_link }}
style={{
alignSelf: 'center',
width: '100%',
height: 300,
}}
/>
</View>
)}
keyExtractor={item => item.id.toString()}
/>
</View>
</View>
);
}
}
How can I make componentDidMount render again?
Not sure what you mean, but I think what you are asking is How can I make componentDidMount *run* again?, and to do that, you would need to have the same code in callbackFunction to run that again. componentDidMount will only run after the first time the component render.
Also notice that if you want to rerender the FlatList you need to pass extraData so it know that it needs to rerender.
I'm creating a small react native app, when i add some code to update some data from firebase it shows me this error on console:
"FirebaseError: Function CollectionReference.doc() requires its first argument to be of type non-empty string, but it was: undefined"
Code of my action:
const updateChat =(newChat)=>{
return (dispatch)=>{
console.log("trying to update: ", newChat);
console.log("trying to update and getting the id: ", newChat.id);
firestore.firestore().collection("chat").doc(newChat.id)
.update(
{
msg: newChat,
}
)
.then(() =>{
dispatch({
type:'UPDATE_CHAT',
})
})
.catch(function(error) {
console.error("Error updating document: ", error);
})
}}
Code of my component:
class SettingsScreen extends React.Component {
static navigationOptions = {
title: 'Chat Screen',
};
state = {
id: "",
chat_input: "",
updated: false,
}
onNewChat = () => {
this.props.addChat(
this.state.chat_input
)
this.setState({
chat_input: ""
});
Keyboard.dismiss();
}
handleUpdate = (id, chat_input) => {
this.setState(
{
id:id,
chat_input: chat_input,
updated: true,
}
)
}
saveUpdate=()=>{
this.props.updateChat(this.state.chat_input)
this.setState({
chat_input: "",
id: "",
})
}
renderItem = ( {item} ) => {
return (
<View style={styles.row}>
<Text style={styles.message} >{item.msg}</Text>
<TouchableOpacity
style={styles.button}
onPress={ () => {this.props.deleteChat(item.id)} }
>
<Image
source={require('../assets/images/trash2.png')}
fadeDuration={0}
style={{width: 30, height: 30}}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.buttonEdit}
onPress={ () => { this.handleUpdate(item.id, item.msg} }
>
<Image
source={require('../assets/images/edit.png')}
fadeDuration={0}
style={{width: 30, height: 30}}
/>
</TouchableOpacity>
</View>
);
}
render() {
const { thread } = this.props || []
if (!thread) {
return (
<View style={styles.container}>
<Text>Loading...</Text>
</View>
)
}
return (
<View style={styles.container}>
<FlatList
data={thread}
renderItem={this.renderItem}
inverted
keyExtractor={(item, index) => index.toString()}
/>
<KeyboardAvoidingView behavior="padding">
<View style={styles.footer}>
<TextInput
value={this.state.chat_input}
onChangeText={text => this.setState({ chat_input: text })}
style={styles.input}
underlineColorAndroid="transparent"
placeholder="Type something nice"
/>
<TouchableOpacity onPress={
this.state.updated
? this.saveUpdate()
: this.onNewChat.bind(this)
}
>
<Text style={styles.send}>Send</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
thread: state.firestore.ordered.chat
}
}
export default compose(
connect(mapStateToProps, {addChat, deleteChat, updateChat}),
firestoreConnect([
{ collection: 'chat'},
]))(SettingsScreen);
You're not passing all the information to your updateChat action. Edit like below
saveUpdate=()=>{
this.props.updateChat({
chatInput: this.state.chat_input,
id: this.state.id
})
this.setState({
chat_input: "",
id: "",
})
}
You'll also need to change your update parameters to this:
firestore.firestore().collection("chat").doc(newChat.id)
.update(
{
msg: newChat.chatInput,
}
)
whats is the problem in my code?
i can't return list view ? If there is a better strategy, please advise me to make my code better
class Search extends Component {
static navigationOptions =
{
title: 'search',
headerBackTitle: null,
};
state = {
search : '',
output : []
}
handleSearch = (text) => {
this.setState({search: text})
}
searchMe = (search) => {
this.setState({output: <SearchExtend />})
}
render() {
return (
<View style={styles.container}>
<TextInput style={styles.input}
onChangeText={this.handleSearch}
>
</TextInput>
<TouchableOpacity
style={styles.submitButton}
onPress={
()=>this.searchMe(this.state.search)
}
>
<Text>Submit</Text>
</TouchableOpacity>
<View>{this.state.output}</View>
</View>
);
}
}
ActivityIndicator is displayed but the listview does not output
class SearchExtend extends Component{
constructor(props) {
super(props);
this.state = {
isLoading: true
}
}
componentDidMount() {
return fetch('http://example.com/games.php')
.then((response) => response.json())
.then((responseJson) => {
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState({
isLoading: false,
dataSource: ds.cloneWithRows(responseJson),
}, function() {
// In this block you can do something with new state.
});
})
.catch((error) => {
console.error(error);
});
}
ListViewItemSeparator = () => {
return (
<View
style={{
height: .5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
}
onPress(item, rowData){
if(item == 0)
{
this.props.navigation.navigate('Third', {rowData},)
}
else {
this.props.navigation.navigate('Second', {rowData},)
}
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={styles.MainContainer}>
<ListView
dataSource={this.state.dataSource}
renderSeparator= {this.ListViewItemSeparator}
renderRow={(rowData) =>
<TouchableOpacity
onPress={()=> this.onPress(rowData.subCategory, rowData)}>
<Text>{rowData.name}{rowData.subCategory != 0 ? '>' : ''}</Text>
</TouchableOpacity>
}
removeClippedSubviews={false}
/>
</View>
);
}
}
Please help me to fix this problem
The rest of the sections and styles were removed because they were not related to the question