FlatList, Props ListHeaderComponent and Dynamic flatlist data - reactjs

Sorry for the title, I wasn't inspired, and I didn't know what title to put for my problem.
I'm using Socket.io on my React native application to render some dynamics datas.
I'm using FlatList to display all this datas, and I need to show some contents like filters and buttons at the top of the FlatList.
So In my FlatList Props, I used ListHeaderComponent and in this component I display one other flatlist and contents.
My problem is :
When the main FlatList is updated with one of the Socket.io response (Every 5sec), all the components displayed by ListHeaderComponent aren't usable for a bit of time.
This period is like 500ms or less, but in the ListHeaderComponent I have some buttons / filters, and they are not clickable during this period.
And the problem is repeated each time the FlatList data is updated.
You can see my code (simplyfied) here :
export default function MyList({ navigation }) {
const [myData, setMyData] = useState<ResponseType[]>([])
// Socket Io
const socket = io(SOCKET_URL, { forceNew: true, timeout: 5000 })
useEffect(() => {
socket.on("connect", () => {
console.log("Connected to Socket ID : ", socket.id)
})
return () => {
socket.disconnect()
socket.close()
}
},[])
socket.on("socketResponse", (data: ResponseType) => {
setMyData(prevDate => [...prevDate, data])
})
const renderFilterItem = ({ item }: { item: Filter }) => <Text key={ item.id }>{ item.title }</Text>
const renderListItem = ({ item }: { item: ResponseType }) => <Text key={ item.id }>{ item.id }</Text>
const ListTop = () => (
<>
<TouchableOpacity onPress={() => navigation.navigate('HomePage')}>
<Text>BACK CONTENT</Text>
</TouchableOpacity>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={someFilters}
keyExtractor={(item) => item.id}
renderItem={renderFilterItem}
/>
</>
)
return (
<FlatList
ListHeaderComponent={ListTop}
data={myData}
keyExtractor={(item) => item.id}
renderItem={renderListItem}
maxToRenderPerBatch={5}
windowSize={2}
/>
)
}
I don't know how to explain better my problem. But If I move the component of ListHeaderComponent above the FlatList, there is no more problem.
But If I do this, just the FlatList is scrollable, and I want all the page scrollable.
I can't wrap my Flatlist with a ScrollView because of this error : VirtualizedLists should never be nested inside plain ScrollViews with the same orientation
Has anyone faced this problem ?
Thanks

try to use optimization for ListTop component as a possible problem here would be rendering of your functional component due to change of parent one.
here is the code sample.
import React, {useCallback} from 'react';
// your other codes
const ListTop = useCallback(() => (
<>
<TouchableOpacity onPress={() => navigation.navigate('HomePage')}>
<Text>BACK CONTENT</Text>
</TouchableOpacity>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={someFilters}
keyExtractor={(item) => item.id}
renderItem={renderFilterItem}
/>
</>
), [someFilters]);
here the component will be memoized until there is no change in someFilters.

Related

How to use the same ref between child/parent component

I am trying to control the carousel in the child component from the parent component.
I have used forward ref on the child component but its not working. Where am I going wrong?
Parent:
const CoachingCarousel = ({}) => {
const carouselRef = useRef<Lottie>(null);
const renderItem = ({item}: any) => {
return (
<View style={styles.renderItemContainer}>
{item.icon}
<Text style={[styles.titletext, spacing.gbMt7]} variant="titleLarge">
{item.title}
</Text>
<Text style={[styles.subtitleText, spacing.gbMt4]} variant="bodyMedium">
{item.text}
</Text>
<Text
style={[styles.next]}
variant="bodyLarge"
onPress={() =>
carouselRef?.current?.goToSlide(
totalSlides !== item.key
? item.key
: () => {
setCoachingScreenCompleted('CoachingScreenCompleted', true),
console.log('Go to homepage');
},
)
}>
{totalSlides !== item.key ? 'Next tbc' : 'Done tbc'}
</Text>
</View>
);
};
return (
<AppCarousel slides={slides} renderItem={renderItem} ref={carouselRef} />
);
};
Child:
const AppCarousel = React.forwardRef(
({style, slides, renderItem}: props, ref) => {
return (
<View style={[styles.container, style]}>
<AppIntroSlider
ref={ref}
renderItem={renderItem}
data={slides}
/>
</View>
);
},
);
Here a is React rule,
1. Do not declare components within other components, this will lead to very weird behaviors in React.
2. Also you cannot share ref between two components at the same time whether they are parent/child or siblings.
Answer
Also from what I am seeing, you are using a ref to keep track of the current slide. ref does not rerender in React so you will not see your changes. Try using useState.
useRef is for non rerendering values or to keep track of DOM nodes which is not necessary in your case.
I found this tutorial to be quite well. One critic would be the over complicated use of React.children.map and React.children.clone although they have their use cases.

React Native - passing data model from Flat list

I have a FlatList:
<ScrollView nestedScrollEnabled={true}>
<Screen style={styles.screen}>
<TouchableOpacity onPress={() => navigation.navigate("ProfileDetailScreen")}>
<FlatList
data={users}
renderItem={renderItem}
keyExtractor={item => item.uid}
/>
</TouchableOpacity>
</Screen>
</ScrollView>
And Im getting some data from firebase like:
useEffect(() => {
firebaseUsers
.onSnapshot(
querySnapshot => {
const newUsers = []
querySnapshot.forEach(doc => {
const user = doc.data()
user.id = doc.id
newUsers.push(user)
});
setUsers(newUsers)
},
error => {
console.log(error)
}
)
}, [])
The data I get from firebase has name, image and age (and more more fields).
How can I pass all the data for a single user into the ProfileDetailedScreen?
Not sure the correct way to achieve this (not sure If I need to create data models etc?)
I don't know firebase that well but I have experience in RN. From what I'm understanding, you're going to want to make the renderItem function in your FlatList a function that returns a TouchableOpacity with navigation to the ProfileDetailedScreen component while also passing the user object. You'll display the user details in the ProfileDetailedScreen.
Passing items in navigation documentation
/* 'item' is a user from the users array you passed into the data property of your FlatList above */
renderItem = ({item}) => (
<TouchableOpacity onPress={() => navigation.navigate("ProfileDetailScreen", item)}>
<Text> item?.name </Text> {/* show brief info about user */}
</TouchableOpacity>
)
If you're using Redux for your application state, you could also navigate only to "ProfileDetailScreen" and dispatch an action that sets a currentUser in state. Your "ProfileDetailsScreen" would then pull the currentUser from state. This way has more moving parts but the component piece would look something like this:
/* 'item' is a user from the users array you passed into the data property in your FlatList above */
renderItem = ({item}) => (
<TouchableOpacity onPress={this.setUserAndNavigate.bind(this, item)}>
<Text> item?.name </Text> {/* show brief info about user */}
</TouchableOpacity>
)
setUserAndNavigate = (user) => {
this.props.setUser(user);
navigation.navigate("ProfileDetailScreen");
}

FlatList rendering is heavy for big data set

In my application I have a FlatList with dataset of 100 items in it. Each item has a complex UI and I noticed that it's taking a heavy toll on performance. The page that has the list takes up to 5 seconds to load.
I noticed that the moment the component is rendered for the first time, the renderItem function of the FlatList is also called for each and every item in my data set, I also noticed that it also happens if there any other setState change for other stuff on that page. Is there a way to prevent this re-rendering of the flat list or at least to re-render only the visible items - just like with Recycle with in native Android?
How can I only render the visible items when the component first appears?
I tried to use initialNumToRender and maxToRenderPerBatch but neither have worked.
Here is an example of the code:
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const App = () => {
const [text, setText] = React.useState('')
const renderItem = ({ item }) => {
console.log("Render Item")
return (<Item title={item.title} />)
};
return (
<SafeAreaView style={styles.container}>
<TextInput
value={text}
onChangeText={(val) => setText(val)}
/>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
initialNumToRender={6}
maxToRenderPerBatch={6}
/>
</SafeAreaView>
);
}
If I try to type something in TextInput the whole list re-renders but nothing in the list has changed.. how can I prevent this?

InfiniteScroll using ScrollView - React Native

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>;

React-native FlatList items not get to right height

I have items in diffrent heights (250 or 150) inside a FlatList,
When iterate each item and append the state of the dataSrouce for the FlatList everything renders alright, but if I want to avoid the "appending" affect and set the dataSrouce to all of the items at once, it seem FlatList have a wierd bug where items are not getting their right height (There is a blank space on the botton where the item had suppose to fill it).
Tried put "flexGrow:1" on the FlatList, tried the "initialNumToRender" property,
Tried to fix height of the each Item in the view.
Container of the FlatList is "flex:1".
My FlatList:
render() {
const _this = this;
const { loading } = this.state;
return (
<Components.ViewContainer>
{this.printTopHeader()}
{loading ? (
<ActivityIndicator size={25} />
) : (
<FlatList
style={{ flex: 1 }}
removeClippedSubviews={true} //tried with and without
data={this.state.posts}
extraData={this.state.posts} //tried with and without
renderItem={({ item }) => (
<HomeCard
post={item}
/>
)}
keyExtractor={(item, index) => item.key}
/>
)}
</Components.ViewContainer>
);
}
Components.ViewContainer:
const ViewContainer = styled.View`
flex:1;
`;
HomeCard:
render() {
const { theme, showActions } = this.props;
const {
imageUrl,
user,
title,
selectedPlace,
textColor,
backgroundColor
} = this.props.post;
return (
<Components.ContainerView>
...
</Components.ContainerView>
);
}
export default withTheme(HomeCard); // styled-components implementation
The issue were caused by a matter of wrong styling applied to the child elements,
By better understand how FlexBox works I managed to figure that I was missing a flex: 1 attribute on the list elements, and therefore the items styling didn't calculated correctly.

Resources