React Native FlatList last item visibility issue - react-native

I am fetching products list and then displaying using a FlatList, my list contains 5 items and as you can see FlatList row height is variable because of varying description text. So the issue is my last item card is not completely visible maybe this is some kind of flat list issue or layout issue. Any help would be highly appreciated
renderProducts() {
if (this.props.loading === true) {
return (
<View style={Styles.spinnerStyle}>
<ActivityIndicator size='large' />
</View>
);
}
return (
<FlatList
data={this.props.myProducts}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Card
title={item.title}
image={{
uri: item.image !== null ? item.image.src :'../resImage.jpg'
}}
>
<Text style={{ marginBottom: 10 }}>
{item.body_html}
</Text>
<Button
icon={{ name: 'code' }}
backgroundColor='#03A9F4'
fontFamily='Lato'
buttonStyle={{ borderRadius: 0, marginLeft: 0, marginRight: 0, marginBottom: 0 }}
title='VIEW NOW'
/>
</Card>
)}
/>
);
}
render() {
return (
<View>
<View style={Styles.viewStyle}>
<Text style {Styles.textStyle}>ProductsList</Text>
</View>
{
this.renderProducts()
}
</View>
);
}

Set bottom padding to the <FlatList> content container:
<FlatList
contentContainerStyle={{ paddingBottom: 20 }}
/>

Add {flex: 1} to the View tag housing the Flatlist component.
In my case,
const App = () => {
return (
<Provider store={createStore(reducers)}>
<View style={{ flex: 1 }}>
<Header headerText={'My App'} />
<ScreenTabs /> // this is my content with FlatList
</View>
</Provider>
);
};
export default App;

Just wrap it in a view with flex:1
<ParentView style={{flex:1}
<View style={{flex:1}}>
// Your flatlist
<View>
</ParentView>
Also, note that the each parent of this "View" in which Flatlist is wrapped must also be a View with Flex of 1. Otherwise, that your flatlist wont be visible.

use contentContainerStyle props of FlatList
<FlatList contentContainerStyle={{ paddingBottom: 20}} />

Latest update:
react-navigation has a SafeAreaView with an option to not show that bottom area.
import { SafeAreaView } from 'react-navigation';
<SafeAreaView forceInset={{ bottom: 'never' }} />
Old response below:
You can't see your list with flex: 1 because flex: 1 will grow the component to the parent. If the parent doesn't have flex: 1, it won't stretch to its parent or the screen. Keep in mind, however, that flex: 1 with a SafeAreaView will cause the bottom safe area to show. This will look bad if your SafeAreaView backgroundColor is a different color from your list's back ground.
My old workaround was to add an item to the bottom of the array of items, but I'm still exploring how to scroll past/under the bottom safe area margin with a FlatList (which is how I found this post to begin with).
Update: Using ListFooterComponent you can create even a plain white "footer" with height and/or a margin
For example (I wouldn't directly copy and paste this if I were you... there's surely a better way to detect bezel-less iPhones, especially in 2019 when we have more than one)
ListFooterComponent={<View style={{ height: 0, marginBottom: 90 }}></View>}
This is how I would do it, using the iPhoneX's height for now. But it's not future-proof since the conditional will need to be updated every time a new iPhone with no bezels comes out:
ListFooterComponent={<View style={{ height: 0, marginBottom: noBezels ? 90 : 0 }}></View>}
Or you could just always have some spacing at the bottom, like a loading gif, a message... whatever.
UPDATE 2:
I found out about react-native-device-info which has a hasNotch() method. I find that useful for styling for iPhones with no bezels by combining hasNotch() with Platform.OS === 'ios'

You can try this solution
For Vertical FlatList:
<FlatList
ListFooterComponent={<View />}
ListFooterComponentStyle={{height:200}}
/>
For Horizontal FlatList:
<FlatList
contentContainerStyle={{paddingRight:40}}
/>

For IOS issues you can apply some IOS specific props:
<FlatList
// ...
contentInset={{top: 0, bottom: 20, left: 0, right: 0}}
contentInsetAdjustmentBehavior="automatic"
// ...
/>
The solution with contentContainerStyle padding didn't seem the best overall for fixing the safe area IOS issues in my case.

Work very well for me
<FlatList
data={data}
contentContainerStyle={{ paddingBottom: 30 }}
style={{height: '95%'}}
renderItem={({ item, index }) => (
<ListItem item={item} onPress={() => handlePress(item, index)} />
)}
/>

Make use of the contentContainerStyle prop in the flatlist
<FlatList contentContainerStyle={{paddingBottom: 10}} />

For dynamic flatlist, you can assign height to the parent view. I resolved it with same.
<View style={{height:'80%'}}>
<Flatlist
extraData={data}
data={data}
renderItem={renderItem}
/>
</View>

I had the same issue and found the solution. To fix the issue just add style={{flex: 1}} for each View element who is a parent for FlatList.
See updated code below.
render() {
return (
<View style={{flex: 1}}> // Here you should add style as {flex: 1}
<View style={Styles.viewStyle}>
<Text style={Styles.textStyle}>ProductsList</Text>
</View>
{ this.renderProducts() }
</View>
);
}

This worked for me.
<View style={{flex: 1}}>
<FlatList
style={{flex: 1}}
data={data}
renderItem={({item}) => (
<ListItem item={item} onPress={() => handlePress(item)} />
)}
/>
</View>

#krish solution is great for the fixed-size list items, however as
#Neeraj Sewani said, it may not be suitable for dynamic size list items.
so you can fix the issue like this -in case direction is column -:
<View style={{height: '90%'}}>
<FlatList/>
</View>
Otherwise, -in case direction is row -:
<View style={{height: '90%', width:'90%'}}>
<FlatList/>
</View>

I was seeing this same problem in our Android + iOS React Native hybrid app. We embed the FlatList component within our native UIs inside a Fragment in Android and we were unable to scroll to the last item in the list, even though the scroll indicator would show that there was more to scroll, the ScrollView would simply not scroll further. I tried all the combinations of using a wrapping <View style={{flex:1}}> to wrap the FlatList as well as using contentContainerStyle={{flexGrow:1}} on the FlatList without success. Pursuing the clue further it turned out that the FlatList needs an absolute, predefined height on Android to allow scroll to the bottom - it works just fine on iOS but on Android using match_parent wasn't going to work. Since we need to support all types of devices, phone and tablet too, it wasn't possible to pre-define an absolute height either.
To fix this, I made a custom FrameLayout subclass to house the ReactRootView's fragment, which overrides onLayout() to ignore the child view measurements, forcing the views to have the exact dimensions of the FrameLayout, somewhat like so in Kotlin:
class StretchFrameLayout #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
for (child in children){
if (child.visibility == View.GONE) continue
child.updateLayoutParams {
this.width = measuredWidth
this.height = measuredHeight
}
if (needsRelayout){
handler.postDelayed({child.requestLayout()},1)
}
}
super.onLayout(changed, left, top, right, bottom)
}
}

This work very well in my case:
<FlatList
data={todos}
contentContainerStyle={{ height: '100%' }}
renderItem={({ item }) => <Todos items={item} pressed={pressed} />}
/>

I've solved it doing contentInset={{ bottom: data.length * itemStyle.height, }} with itemStyle.height being 50 worked fine.

Related

Last Image is being cropped, when using ScrollView in react-native

I am trying to build something like instagram posts, that is continuous images that can be scrolled. But the last image is being cropped, that is only the upper half of it is being visible, there are several posts, regarding the same, but those didnt help, (contentContainerStyle={{flexGrow: 1,}}, adding height to a invisible view). Can someone please point out what is going wrong?
EDIT: I have changed scrollview to flatlist and still face the same problem, can you suggest what else to do?
EDIT 2: realised that the <Header /> and <Stories /> above the flatlist are not letting it scroll completely, that is the height that
it is not scrolling is proportional to height of <Header /> and <Stories />
post.js
const Post = ({post}) => {
return (
<View style={{flex:1}}>
<Divider width = {0.5}/>
<PostHeader post={post}/>
<PostImage post={post} />
<PostFooter post={post}/>
</View>
)
}
const PostImage = ({post}) => {
return (
<View style={styles.postContainer}>
<Image style={styles.image} source={{uri: post.post_url}}></Image>
</View>
)
}
const styles = StyleSheet.create({
container: {
},
dp: {
width: 35,
height: 35,
margin:5,
borderRadius: 20,
borderWidth : 1,
borderColor : '#ff8501'
},
postContainer: {
width: '100%',
height: 400,
},
image: {
height: '100%',
resizeMode: 'cover',
}
})
homescreen.js
const HomeScreen = () => {
return (
<SafeAreaView >
<Header />
<Stories />
{/* <ScrollView>
{
POSTS.map((post, index) => {
return (
<Post key={index} post={post} />
)
})
}
</ScrollView> */}
<FlatList data={POSTS} renderItem={({item}) => <Post post={item} />} />
</SafeAreaView>
)
}
If you want to render repetitive view so why you are not using Faltlist instead of Scrollview. For repetitive view react native provide one component which is called Flatlist and pass you array data in render item it will give you better performance as well.
<SafeAreaView style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</SafeAreaView>
const renderItem = ({ item }) => (
<Divider width = {0.5}/>
<PostHeader post={item}/>
<PostImage post={item} />
</View>
);
const styles = StyleSheet.create({
container: {
flex: 1,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
});
According to React Native docs FlatList is the Component you should use:
ScrollView renders all its react child components at once, but this has a performance downside.
Imagine you have a very long list of items you want to display, maybe several screens worth of content. Creating JS components and native views for everything all at once, much of which may not even be shown, will contribute to slow rendering and increased memory usage.
This is where FlatList comes into play. FlatList renders items lazily, when they are about to appear, and removes items that scroll way off screen to save memory and processing time.
FlatList is also handy if you want to render separators between your items, multiple columns, infinite scroll loading, or any number of other features it supports out of the box.
const Post = () => {
renderItemHandler = ({post, index}) => (
<View key={index} >
<Divider width={0.5}/>
<PostHeader post={post}/>
<PostImage post={post} />
</View>
)
return (
<SafeAreaView style={{flex: 1}}>
<View style={{height: "90%"}}>
<Flatlist
data={POSTS}
renderItem={renderItemHandler}
keyExtractor={item => item.id}
/>
</View>
</SafeAreaView>
)
}

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}
/>

Add one last element to the end of FlatList

I am trying to achieve the following, whereas all but the last are normal images coming from an array.
Currently this is the code for the FlatList:
<FlatList
data={images}
numColumns={3}
// ListFooterComponent={
// <View style={[StyleSheet.actionUploadedPictureFrame , {height: PIC_HEIGHT, width: PIC_WIDTH}]}>
// <Image source={require('../../../images/add-icon.png')} />
// </View>}
renderItem={ ({item, index}) => index == images.length -1 ?
<View style={{flexDirection: 'row'}}>
<Image style={[StyleSheet.actionUploadedPictureFrame , {height: PIC_HEIGHT, width: PIC_WIDTH}]} source={{uri: item.url}} />
<View style={[StyleSheet.actionUploadedPictureFrame , {height: PIC_HEIGHT, width: PIC_WIDTH}]}>
<Image source={require('../../../images/add-icon.png')} />
</View>
</View>
: <Image style={[StyleSheet.actionUploadedPictureFrame , {height: PIC_HEIGHT, width: PIC_WIDTH}]} source={{uri: item.url}} /> }
keyExtractor={(item, index) => index.toString()}
/>
Which long story short, renders the pictures in the array in a table of 3 columns, and checks for the last picture in the list and in addition to rendering the picture, it also renders this plus sign.
However this is bound to have some sort of error if the list has a number of elements multiple of 3, since the plus sign would most likely be out of the screen.
If I use ListFooterComponent, it renders it in an entirely new line.
Does anyone know an effective work around to this?
Since I don't think I was clear enough, I'll try to explain properly my idea with this sample code:
<FlatList
data={[...images, { plusImage: true }]}
numColumns={3}
renderItem={({ item }) => {
if (item.plusImage) {
return (
<View
style={[
StyleSheet.actionUploadedPictureFrame,
{ height: PIC_HEIGHT, width: PIC_WIDTH }
]}
>
<Image source={require("../../../images/add-icon.png")} />
</View>
);
}
return (
<Image
style={[
StyleSheet.actionUploadedPictureFrame,
{ height: PIC_HEIGHT, width: PIC_WIDTH }
]}
source={{ uri: item.url }}
/>
);
}}
keyExtractor={(item, index) => index.toString()}
/>;
I don't know if it's the best practice to concat data array like that directly in the data prop, but you can always choose to declare it earlier in the render method.
Let me know if this can fit your request.
I also came around the same situation and found a good way when I have check the documentation of FlatList so the best way that I recommend is to use ListFooterComponent you can pass a style object for that element using ListFooterComponentStyle Click here for more details.

FlatList not filling 100% height in react-native

I got a FlatList that occupies the 50% of the vertical space on the screen.
This FlatList has only a few items, and because of this, is not taking the entire space but only the half of it, and when I scroll up/down, it looks as if it has overflow: hidden.
I read this in the RN GitHub about it, they recommended using flexGrow: 1 on the FlatList's contentContainerStyle, also the parent View with flex: 1. But is not working. Is this still working? They said to use it on ScrollViews, but isn't FlatList inherited from ScrollView or something like that?
This is my current structure:
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
contentContainerStyle={{ flexGrow: 1 }}
numColumns={2}
style={ styles.cardContainer }
keyExtractor={ this._keyExtractor }
data={ this.state.listData }
renderItem={ this._renderItem }
>
</FlatList>
</View>
);
}
You actually want to put flex onto your FlatList. Currently you have it on your contentContainerStyle.
So flex:1 on your FlatList will make it fill the rest of the container.
You'll want to adapt this to your solution depending on what you currently have in styles.cardContainer.
<View style={{ flex: 1 }}>
<View style={{ height: Dimensions.get("window").height * 0.25 }} />
<FlatList
numColumns={2}
style={{ flex: 1 }}
keyExtractor={this._keyExtractor}
data={["1", "2", "3"]}
renderItem={this._renderItem}
/>
</View>
Set the contentContainerStyle of the FlatList to flexGrow: 1 will not block the scroll functionality on long list.
There might have issue with the old version, but latest works just fine.
https://github.com/facebook/react-native/issues/17944
To add onto this 2 years later, i had a justifyContent:center on my list container and that was what made the flex not fill the entire space. Otherwise flexGrow works.

React Native ScrollView snapping back to top after release

I have used ScrollView in other apps adding just a style={styles.container} with the styles. However in this app I am creating in my styles I have alignItems:'flex-start' which throws an error with just style={styles.container} and instead you need to pass in alignItems:'flex-start' through contentContainerStyle={styles.container}.
Error: Invariant Violation: ScrollView child layout (["alignItems"]) must by applied through the contentContainerStyle prop.
However when I add contentContainerStyle when scrolling down in the view, once the finger is lifted off the phone (or release of the mouse in simulator), the scroll automatically goes back to the top. If I just use style={styles.container} and take out the alignItems:'flex-start' it scrolls correctly, but my items in the UI are not laid out how I need them. What is causing it to scroll back to the top with contentContainerStyle and is there a fix?
render:
var _that = this;
var iconsToShow = icons.map(function (icon, i){
if(i < 81){
return (
<TouchableHighlight
onPress={() => _that.changeIcon(indexToChange, icon)}
underlayColor='#F7F7F7'
key={i}>
<Text style={styles.iconText}><IonIcons name={icon} size={30} color="#555" /></Text>
</TouchableHighlight>
);
}
});
return (
<Carousel width={SCREEN_WIDTH} animate={false} indicatorColor="#444" inactiveIndicatorColor="#999" indicatorAtBottom={false} indicatorOffset={16}>
<View>
<ScrollView contentContainerStyle={styles.container}>{iconsToShow}</ScrollView>
</View>
<View>
//next page for carousel
</View>
</Carousel>
);
I figured out how to get it to scroll. Instead of having the View wrapping the ScrollView and the ScrollView having any flex styling or alignItems:'flex-start' with contentContainerStyle={styles.container}, put that on the View which is a child of the ScrollView and just use style= instead of contentContainerStyle=.
render:
<ScrollView style={styles.container}>
<Text style={styles.goalName}>{goal}</Text>
<View style={styles.viewContainer}>
{iconsToShow}
</View>
</ScrollView>
Styling:
var styles = StyleSheet.create({
container: {
backgroundColor: 'transparent',
paddingLeft:20,
paddingRight:20
},
viewContainer:{
flexDirection:'row',
flexWrap: 'wrap',
alignItems: 'flex-start',
flex: 1
},
iconText:{
paddingLeft:15,
paddingRight:15,
paddingTop:15,
paddingBottom:15
},
goalName:{
textAlign:'center',
marginTop:40,
marginBottom:10,
fontSize:20
}
});
If someone still couldn't fix the problem, try put {flex: 1} into "all" parents of the ScrollView