React Native Flatlist renderItem - react-native

Working with React Native, having some issues with the FlatList component.
This is my FlatList
<FlatList
data={this.state._data}
renderItem={() => this.renderItem()}
refreshControl={
<RefreshControl
onRefresh={() => this.handleRefresh}
refreshing={this.state.refreshing}
/>
}
/>
This is my renderItem function:
renderItem({item, index}) {
return (
<View style={{marginTop: 10, marginHorizontal: 10, paddingLeft:
10}}>
<ListItem
roundAvatar
title={`${item.itemName}`}
subtitle={`${item.amount}`}
avatar={require('../../../images/logo.png')}
/>
<View
style={{
paddingBottom: 10,
paddingTop: 10,
display: 'flex',
flexDirection: "row",
justifyContent: "space-around",
alignContent: "center"
}}
>
<View style={{ flexDirection: "row", alignContent:
"center", width:"45%"}}>
<Button
block
small
// disabled={this.state.acceptButtonGray}
style=
{this.state.acceptButtonGray ? ({
backgroundColor: 'gray',
width: "100%"
}) : ({backgroundColor: "#369ecd",
width: "100%"
})}
onPress={() =>
this.setState({
modalVisible: true,
// acceptOrDeclineModalText: `Accept offer for ${item.amount} ${'\b'} Are you Sure?`,
acceptOffer: true,
})
}
>
<Text>
Accept
</Text>
</Button>
</View>
</View>
</View>
);
}
this.setState in the onPress in the button should make a Modal visible, and set acceptOffer to true. Modal opens, user confirms their offer. The offer button which opened that modal now should be gray, and even better, disabled.
Passing my RenderItem function as shown above, I receive
TypeError: Cannot read property 'item' of undefined.
Passing my RenderItem function like this:
renderItem={this.renderItem}
I Get This Error:
_this2.setState is not a function
The FlatList Component is certainly responsible for part of my issue, as well as how and where I am calling this.setState. Only one button is shown in my post, but there are two, one for accept, one for decline. Would having two modals change anything?
The FlatList displays my ListItem components with ease until I attempt to call this.setState in the buttons inside the View which contains those ListItems.
The Modal close button takes this.state.acceptOffer and if true, sets this.state.acceptButtonGray to true, should this logic be somewhere else?
Is there another way to open a modal and change the button color without using component state? Does react want these buttons inside of a TouchableOpacity?
I greatly appreciate any help given.

you should write a renderItem function like this
renderItem = ({item, index}) => {
// return here
}

Change your renderItem method to renderItem={this.renderItem.bind(this)}?

As per my Knowledge item and index are passed as object in flatlist's renderItem
so we can pass by two ways
1 way
Flatlist Component
<FlatList
data={this.state.data}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => this._renderItem(item, index)} //Passing as object from here.
/>
Render Item
_renderItem = (item, index) => {
console.log(item)
console.log(index)
}
2 way
Flatlist Component
<FlatList
data={this.state.data}
keyExtractor={(item, index) => index.toString()}
renderItem={( item, index ) => this._renderItem(item, index)}
/>
Render Item
_renderItem = ({item, index}) => { // Passing as object from here
console.log(item)
console.log(index)
}

1) You can write function as -
renderItem = ({item, index}) => {
// return here
}
2) or else if you want to execute your function then -
<FlatList
data={this.state._data}
renderItem={(item) => this.renderItem.bind(this, item)}
refreshControl={
<RefreshControl
onRefresh={() => this.handleRefresh}
refreshing={this.state.refreshing}
/>
}
/>

You have to use bind(this,item) or change function like (item)=>.

I experienced the same issue and wasted many hours to figure out why it was not re-rendering:
We need to set extraData prop of FlatList if there is any change in the state like so:
<FlatList data={this.state.data} extraData={this.state} .../>
Please see the official documentation here:
https://facebook.github.io/react-native/docs/flatlist.html

Related

FlatList initialScrollIndex not working as expected

I am using a horizontal FlatList as menu bar for my application. The FlatList must scroll to the clicked item and I am using initialScrollIndex for this.
But the problem is that it scrolls but the content is not visible many times.
My code
<FlatList
ref={listRef}
data={headerMenuChannels}
style={{
backgroundColor: applicationTopBar.buttonBarColor,
width: Dimensions.get('window').width,
}}
showsHorizontalScrollIndicator={false}
horizontal={true}
onScrollToIndexFailed={error => console.log('scroll failed', error)}
initialScrollIndex={screenNumber}
renderItem={v => (
<View
key={v.index}
onLayout={ref => onMenuLayout(ref, v.index)}>
<Text
onPress={() => {
setScreenNumber(v.index);
}}>
{v.item}
</Text>
</View>
)}
/>
Video of the problem
here
With initialScrollIndex you should use getItemLayout prop. Something like this:
getItemLayout={(data, index) => ({
length: width,
offset: width * index,
index,
})}

ERROR - VirtualizedLists should never be nested inside plain ScrollViews with the same orientation

I'm working on a react-native app and I have to put a list of object in a Scrollview, so I use the FlatList component to do it. This is the piece of code that generates the error:
<ScrollView contentContainerStyle={style}>
Other components
<FlatList
style={style}
data={data}
scrollEnabled={false}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index}) => (somethings)}
/>
Other components
</ScrollView>
The complete error is: VirtualizedLists should never be nested inside plain ScrollViews with the same orientation because it can break windowing and other functionality - use another VirtualizedList-backed container instead.
Avoid using FlatList with the same orientation. Instead, restructure your code like this --
<ScrollView contentContainerStyle={style}>
Other components
{
data.map((item)=> <Somthing item={item}/>)
}
Other components
</ScrollView>
Flatlist has its own ScrollView you can scroll through the list using that so there is no need to put a flatlist into a ScrollView that is why its giving a warning, the both scrollview will clash and one of them (mostly the parent one) works.
The error is self explanatory and it should be in a developers best interest to avoid these kind of things even when it's just a false alarm.
Your particular situation could use the following solution:
<FlatList
data={data}
keyExtractor={(item, index) => `key-${index}`}
ListHeaderComponent={() => (
<SomeComponents>
...Some components those need to be on top of the list
</SomeComponents>
)}
ListFooterComponent={() => (
<SomeComponents>
...Some components those need to be below the list
</SomeComponents>
)}
renderItem={({ item, index}) => (somethings)}
/>
Another note, if you need more complex list that needs header and footer for the list itself, you can try SectionList.
Your component FlatList and ScrollView have the same orientation(vertical), so you need put your component inside a ScrollView with horizontal orientation like this:
<View>
<ScrollView nestedScrollEnabled={true} style={{ width: "100%" }} >
<View>
<ScrollView horizontal={true} style={{ width: "100%" }}>
<FlatList />
</ScrollView>
</View>
</ScrollView>
</View>
Solution 1: Use FlatList props ListHeaderComponent and create all of your page top section in that. Something like this:
This will not show any warning or error.
Solution 2:
Because only parent view will scroll (ScrollView) and not the child FlatList, so to get rid of the warning you can pass a prop scrollEnabled={false} to the FlatList.
If it doesn't go then import LogBox from react-native and write this in your component
useEffect(() => {
LogBox.ignoreLogs(["VirtualizedLists should never be nested"])
}, [])
hopefully, the warning will be removed.
Anyone want to solve this issue can use a custom VirtualizedScrollView like this:
import React from 'react';
import { FlatList } from 'react-native';
const VirtualizedScrollView = props => {
return (
<FlatList
{...props}
data={[]}
keyExtractor={(e, i) => 'dom' + i.toString()}
ListEmptyComponent={null}
renderItem={null}
ListHeaderComponent={() => (
<>{props.children}</>
)}
/>
);
};
export default VirtualizedScrollView;
Then if you use FlatList inside VirtualizedScrollView, it won't get the warning/error.
<VirtualizedScrollView>
<FlatList
/*--- your props ---*/
/>
</VirtualizedScrollView>
There is a npm package where I get this code, you can also use this package
Solution:
I have also encountered same problem with FlatList. Then the package below solved my problem.
'react-native-virtualized-view'
import { ScrollView } from 'react-native-virtualized-view'
if ScrollView is Vertical change Flatlist Horizontal
<ScrollView >
<FlatList
horizontal
data={lenders}
keyExtractor={(_, index) => index}
renderItem={(item) => {
return <Text>item</Text>
}}
/>
You can solve the 2 vertical ones(I'm assuming their side by side, separated with a segemented control?) by using the same flat list and switching out the data when it's switched. If they're just two vertical flat list's one after another use the SectionList.
For the horizontal one you can try putting the Horizontal FlatList in the ListHeaderComponent of the vertical FlatList and see what happens. It can be janky if you use a vertical FlatList in a vertical scroll view but maybe with two different axis it might be ok. The other option is two only show a few items in the horizontal scrollview and have a "Show More".
The last option is too re design/rethink the page so it's not doing so much. On mobile less is more and developers/designers like to get in the mindset of porting desktop thinking onto mobile. Might be worth a shot.
I used the SectionList approach to solve this & wanted to post a code example because I found the Section data required by React Native to be clear but also quite prescriptive.
renderList = ({empty, posts}: {empty: boolean, posts: Array<Object>}) => (
<SectionList
sections={[
{type: 'MAP', data: [{}]}, // Static sections.
{type: 'PROFILE', data: [{}]},
{type: 'POSTS', data: posts} // Dynamic section data replaces the FlatList.
]}
keyExtractor={(item, index) => index}
renderItem={({item, section}) => {
switch (section.type) {
// Different components for each section type.
case 'MAP':
return <MapView />;
case 'PROFILE':
return <Profile />;
case 'POSTS':
return <Post item={item} />;
default:
return null;
}
}}
ItemSeparatorComponent={() => <Separator />}
ListFooterComponent={() => <>{empty && <EmptyList />}</>}
/>
);
What's nice is that the content feels logically quite separate, so you can add sections easily or have different dynamic data sources.
(If you're building a form & want better keyboard handling, you could also try a KeyboardAwareSectionList from react-native-keyboard-aware-scroll-view.)
Flatlist has an integrated scrollview itself, so you can resolve this error by removing ScrollView Component, And let just the Fatlist component
Error ? you are trying to render a FlatList component inside a scrollview component, this is what is throwing the warning.
solution Render the components using Flatlist's ListHeaderComponent={} prop, i.e in your flatlist add the prop as follows
const FlatList_Header = () => {
return (
<View style={{
height: 45,
width: "100%",
backgroundColor: "#00B8D4",
justifyContent: 'center',
alignItems: 'center'
}}
>
<Text style={{ fontSize: 24, color: 'white' }}> Sample FlatList Header </Text>
</View>
);
}
<FlatList
data={BirdsName}
renderItem={({ item }) => <ItemRender name={item.name} />}
keyExtractor={item => item.id}
ItemSeparatorComponent={ItemDivider}
**ListHeaderComponent={FlatList_Header}**
ListHeaderComponentStyle={{ borderBottomColor: 'red', borderBottomWidth: 2 }}
/>
Note the use of the ListHeaderComponent in the code above, that should supress the warning.
Use flatList like this ListHeaderComponent and ListFooterComponent:
<FlatList ListHeaderComponent={
<ScrollView
style={styles.yourstyle}
showsVerticalScrollIndicator={false}
>
<View style={styles.yourstyle}>
</View>
</ScrollView>
}
data={this.state.images}
renderItem={({ item, index }) => {
return (
<View
style={styles.yourstyle}
>
<Image
source={{
uri: item,
}}
style={styles.yourstyle}
resizeMode={"contain"}
/>
<Text
numberOfLines={2}
ellipsizeMode="tail"
style={styles.yourstyle}
>
{item.name}
</Text>
</View>
);
}}
keyExtractor={({ name }, index) => index.toString()}
ListFooterComponent={
<View style={styles.yourstyle}></View>
}
/>
In my case it was happening due to nesting of ScrollView.
Try replacing some of the ScrollView from children components with React.Fragment.
The solution is very simple, please do not put the Flatlist component in the ScrollView.
They both have the same functionality but Flatlist has advantages and is more stable to use.

Conditionally style not working in react native

I followed this answer to dynamically style my component.
Here is my render method :
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.images}
numColumns={2}
keyboardShouldPersistTaps={'always'}
keyboardDismissMode={'on-drag'}
keyExtractor={item => item.localIdentifier}
renderItem={({ item, index }) =>
<TouchableHighlight
underlayColor='transparent'
onPress={() => this.openImage(index)}
onLongPress={() => this.startSelection(item)}
>
<View style={[styles.albumContainer, (this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]}>
<Image
style={styles.albumThumbnail}
source={item.image}
/>
</View>
</TouchableHighlight>
}
/>
</View>
);
}
As you can see I am displaying image thumbnail with TouchableHighlight and FlatList. When user will press and hold on any image thumbnail I called startSelection() with particular flatlist item which then add that item to state. I used that state to set style dynamically of my image as :
<View style={[styles.albumContainer, (this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]}>
<Image
style={styles.albumThumbnail}
source={item.image}
/>
</View>
Here is startSelection() method :
startSelection(item) {
let temp = this.state.selectedItems;
temp.push(item);
this.setState({
selectedItems : temp
});
}
Here is my stylesheet :
const styles = StyleSheet.create({
selectedItem: {
borderWidth: 3,
borderColor: '#22aaff',
},
unselectedItem: {
borderColor: '#000000',
}
});
But when user press and hold that view, item will added to state but style is not changing.
Please help me what's going wrong here !!!
This can be found on FlatList docs:
This is a PureComponent which means that it will not re-render if props remain shallow-equal. Make sure that everything your renderItem function depends on is passed as a prop (e.g. extraData) that is not === after updates, otherwise your UI may not update on changes. This includes the data prop and parent component state.
So you can add extraData to your FlatList component like this:
FlatList Component:
<FlatList
data={this.state.images}
extraData={this.state} //add this!
numColumns={2}
keyboardShouldPersistTaps={'always'}
keyboardDismissMode={'on-drag'}
keyExtractor={item => item.localIdentifier}
renderItem={({ item, index }) =>
<TouchableHighlight
underlayColor='transparent'
onPress={() => this.openImage(index)}
onLongPress={() => this.startSelection(item)}
>
<View style={[styles.albumContainer, (this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]}>
<Image
style={styles.albumThumbnail}
source={item.image}
/>
</View>
</TouchableHighlight>
}
/>
P.S: If your component state has variables which should not re-render FlatList, you would be better of using extraData = {this.state.selectedItems}, but then you need to make sure you pass a different reference to selectedItems when you call setState on startSelection. Like this:
startSelection(item) {
let temp = [...this.state.selectedItems];
temp.push(item);
this.setState({
selectedItems : temp
});
}
Wrap them with extra []
style={[styles.albumContainer, [(this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]]}

React Native FlatList not scrolling to end

I've got (what I thought was) a simple FlatList which renders a list of Cards (code below)
Problem: the list renders, but won't scroll to fully display the last element in the list, OR to the content below the FlatList
What I've tried: basically everything in related SO questions:
Removing ALL styling
Wrapping the FlatList in a View or a ScrollView or both
Adding style={{flex: 1}} to the FlatList or wrappers (this causes **ALL* content to disappear)
Any ideas?
<FlatList
data={props.filteredProducts}
renderItem={({item}) => (
<TouchableOpacity onPress={() => props.addItemToCart(item)}>
<Card
featuredTitle={item.key}
image={require('../assets/icon.png')}
/>
</TouchableOpacity>
)}
keyExtractor={item => item.key}
ListHeaderComponent={
<SearchBar />
}
/>
...
<Other Stuff>
Add style={{flex: 1}} for each View element who is a parent for FlatList.
I had the same issue and just found the solution. Add the prop ListFooterComponentby giving it a React element/component. I just passed a view with the preferable height and it worked for me perfectly.
Here is the code snippet:
<FlatList
data={DATA}
renderItem={({ item }) => (<ListItem itemData={item} />) }
keyExtractor={item => item.id}
ListFooterComponent={<View style={{height: 20}}/>}
/>
This works 100%!
The ultimate solution is ✔
Any view/subview that holds a flatlist must have a flex of 1 - Regardless of how deep.
So if you apply this and it's not working, check your styling and make sure there's no mistake somewhere.
I'm not sure if your scenario is exactly the same as one I encountered on a project a few months ago, but I noticed that I had to add a margin/padding (depends on what you prefer) to lift the bottom of the scrollable container. This was usually because a parent container seemed to interfere with the styling of the scrollable element.
Have you tried adding flexGrow: 1 to your styling in place of flex?
My case is a little different, in that I used FlatList inside bottom sheet provided by reanimated-bottom-sheet package. But the problem was the same: scrolling didn't show the last item properly.
The way I did was I calculated and set the height of the view that included the FlatList. To make it look better when there is bottom inset, I gave more bottom margin to the last item of the FlatList by doing like this:
<FlatList
data={results}
keyExtractor={(item) => item.name}
scrollEnabled={bottomSheetIndex == 1 ? true : false}
renderItem={({ item }) =>
<LocationInfo
bottom={results[results.length - 1].id == item.id ? insets.bottom : 0}
result={item}
wait={waitState[item.id]}
bsIndex={bottomSheetIndex}
/>
}
/>
const LocationInfo = ({ bottom, result, wait, bsIndex }) => {
return (
<View style={[styles.container, { paddingBottom: bottom }]}>
...
for insets, see react-native-safe-area-context.
Remove flex:1 from the FlatList and leave only the parent View.
Add flex: 1 to child into renderItem
<View style={{ height: `93%` }}>
<FlatList
contentContainerStyle={{ minHeight: `100%` }}
scrollEnabled={true}
...props
renderItem={({item}) => (
<View style={{ flex: 1 }}>
...
</View>
)}
I had the same problem. What I did was to provide a marginBottom to the parent view of the FlatList with size that is equivalent (or a little larger) than the size of the rendered item .
<View style={{marginBottom: HEIGHT_OF_CELL + 60, flexGrow:1}}>
<FlatList
keyExtractor={(item, index) => index.toString()}
data={posts}
renderItem={renderItem}
/>
</View>
I method I used to resolve this is by setting the parent view to the FlatList's background color to a visible color(e.g red). Then adjust the margin bottom for that view so that it is large enough for all the contents in the FlatList to be viewed.
In my case the parent view was wrapped in another view which I could not give the flex: 1 style to.
use dimensions for parent view and set height as given:
const screenHeight = Dimensions.get('window').height - 100
<View style={{...styles.container,height:screenHeight}}>
....All child components
</View>
adding flex:1 to my render item worked for me
<FlatList
data={data}
renderItem={({ item }) => (
<ListItem
onPress={() => console.log('ok')}
bottomDivider
rightTitle={
<Divider style={{ marginBottom: 4, backgroundColor: item.status, width: '50%', height: '11%', borderRadius: 10 }} />
}
titleProps={{ numberOfLines: 1 }}
rightSubtitle={item.date}
rightTitleStyle={{ fontFamily: 'poppins' }}
rightSubtitleStyle={{ fontFamily: 'poppins' }}
titleStyle={{ fontFamily: 'poppins' }}
subtitleStyle={{ fontFamily: 'poppins' }}
title={item.device}
subtitle={item.type}
leftAvatar={{ source: item.icon, rounded: false }}
**style={{ flex: 1 }}**
/>
)}
keyExtractor={item => item._id}
ListHeaderComponent={renderHeader}
ListFooterComponent={renderFooter}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.2}
/>

Passing data from List Item to child component react native

I have Flatlist display data in a ListItem from a fetch request as shown.
ListTrips.js
export default class ListTrip extends Component {
....
toggleModalConfirmTrip = item => {
if (this.ModalConfirmTrip) {
this.ModalConfirmTrip.toggleModal();
}
};
<FlatList
data={data}
renderItem={({ item }) => (
<ListItem
*onPress={() => this.toggleModalConfirmTrip(item)*}
roundAvatar
title={`${item.location_from} to ${item.location_to} `}
subtitle={item.user[0].name}
rightTitle={item.timeStamp}
avatar={
<Image
source={require('../assests/carLogo.png')}
style={{ width: 40, height: 50, borderRadius: 10 }}
/>
}
containerStyle={{ borderBottomWidth: 0, paddingBottom: 10 }}
/>
)}
// Uses object ID to iterate over the trips
keyExtractor={item => item._id}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
refreshing={refreshing}
onRefresh={this.handleRefresh}
.
.
.
I want to pass the data of the selected item to a modal component (a child of ListTrips) so I can display it. I attempted to do it by passing item in onPress={() => this.toggleModalConfirmTrip(item)} but it didn't work.
I know I have to pass it as a prop down to my child component but I'm sure how.
ModalConfirmTrip.js
I believe your ListItem must be having access to item as a prop, the ListItem being the stateless React component there. So you should call the onPress of your touchable inside the ListItem with the item parameter.
const ListItem = ({item, onPress, otherProps}) => {
return (
...
<Button onPress={() => onPress(item)} />
...
);
}
Then your ListItem should be called/rendered as follows:
<ListItem
onPress={(item) => this.toggleModalConfirmTrip(item)}
roundAvatar
... />
I hope this helps. If you have any third party UI library for the ListItem then please specify that.