I have a FlatList component, consisting of 3 sections:
<View style={{ flex: 1 }}>
<FlatList
ListHeaderComponent={Comp1}
ListFooterComponent={<Comp2 style={{ flexGrow: 1, justifyContent: 'flex-end' }}/>}
renderItem={Comp3}
contentContainerStyle={{ flexGrow: 1 }}
/>
</View>
By default, the ListFooterComponent will render right after the ListHeaderComponent, if data.length is 0.
I need to render it at the bottom all the time.
One workaround I've found so far is to provide an empty view for ListEmptyComponent. In this case, it looks fine, until I add at least one item - then it sticks to the top again.
Is it possible to attach ListFooterComponent to the bottom by default?
The blue color is the FlatList, the red color - ListFooterComponent
If it needs to be on the bottom of the screen at all times, you can wrap the separate parts in a ScrollView
render() {
return (
<ScrollView style={{flex: 1}}>
<Comp1/>
<FlatList
style={{flex: 1}}
renderItem={Comp3}
/>
<Comp2/>
</ScrollView>
);
}
Before rederising your View, a good idea is to set your height according to the size of the screen. Something like:
const {height} = Dimensions.get ('window');
The View would look like this:
<View style = {{flex: 1, height: height}}>
Add position: 'relative' to the View:
<View style = {{flex: 1, height: height, position: 'relative'}}>
Then add ListFooterComponentStyle to FlatList:
ListFooterComponentStyle = {{
backgroundColor: '# ccc',
position: 'absolute,
width: '100%',
bottom: 0
}}
Show a complete example function component:
const {height} = Dimensions.get('window'); //capture the screen size
return (
<SafeAreaView style={{flex:1,height:height,backgroundColor:'#f5f5f5', position:'relative'}}>
<FlatList
data = {YOUR_DATA}
renderItem = {renderItem}
keyExtractor = {item => item.idItem}
numColumns = {2} // Divide list items into 2 columns (optional)
onEndReached = {LOAD_MORE_DATA}
onEndReachedThreshold = {0.1} //0.1 = 10%
ListFooterComponent = {YOUR_COMPONENT_FOOTER}
ListFooterComponentStyle={{
backgroundColor:'#ccc',
position:'absolute',
width:'100%',
bottom:0
}}
/>
</SafeAreaView>
)
add flexGrow: 1 to contentContainerStyle of Flatlist
add flexGrow: 1 to ListFooterComponentStyle of Flatlist
add flex: 1 and justifyContent: "flex-end" to View of container used in ListFooterComponent
<FlatList
contentContainerStyle = {{flexGrow: 1}}
listFooterComponentStyle = {{flexGrow: 1}}
listFooterComponent = {()=>(
<View style={{
flex:1,
justifyContent: "flex-end"
}}>
...Component you want at bottom
</View>
)}
/>
Related
for some reason a ScrollView in one of my components isn't working despite working in every other component I've implemented it in. Attempting to implement solutions to similar problems seems to just make the content I want displayed disappear.
I'm expecting to have a scrollable list of sample restaurant dishes. I created some dummy data to pass in for now but noticed it didn't scroll after reaching the end of the phone screen.
const testFoods = [
{
title: "test",
description: "Lorem Ipsum",
price: "$7.77",
image: "dummyLink",
},
// Same as above but 4 more times, didn't want to clog up the description
];
export default function MenuItems() {
return (
<ScrollView showsVerticalScrollIndicator={false}>
{testFoods.map((food, index) => (
<View key={index}>
<View style={styles.menuItemStyle}>
<FoodDetails food={food} />
<FoodImage food={food} />
</View>
</View>
))}
</ScrollView>
);
}
const FoodDetails = (props) => (
<View style={{ width: 240, justifyContent: "space-evenly" }}>
<Text style={styles.titleStyle}>{props.food.title}</Text>
<Text>{props.food.description}</Text>
<Text>{props.food.price}</Text>
</View>
);
const FoodImage = (props) => (
<View>
<Image
source={{ uri: props.food.image }}
style={{
width: 100,
height: 100,
borderRadius: 8,
}}
/>
</View>
);
const styles = StyleSheet.create({
menuItemStyle: {
flexDirection: "row",
justifyContent: "space-between",
margin: 20,
},
titleStyle: {
fontSize: 19,
fontWeight: "600",
},
});
The result is like so
Result with code above
The Header component with the sample restaurant image is a separate component by the way.
I have more data that can't be seen as for whatever reason the screen refuses to scroll. I'm using my actual phone for the tests but the result is the same when I use an emulator, scrolling doesn't work. After looking online I thought I would try adding a parent View with flex: 1 and a flexGrow: 1 to contentContainerStyle inside the ScrollView like so.
export default function MenuItems() {
return (
<View style={{ flex: 1 }}>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ flexGrow: 1 }}
>
{testFoods.map((food, index) => (
<View key={index} style={styles.menuItemStyle}>
<FoodDetails food={food} />
<FoodImage food={food} />
</View>
))}
</ScrollView>
</View>
);
}
But that only resulted in the content disappearing. Reloading the app didn't change anything either Result after trying above code
Attempting to use a FlatList had the same result. I've also read that having percentage based styling values on height for any of the children components can make the scrolling stop working but all my components don't utilize such. Oddly enough when I change the styling on the outer view to height: 400, I'm able to get the scrolling to work but this is very sloppy and will likely only work on phone screens similar to mine. I know the ScrollView component is working fine, as when I add "horizontal" to it the scrolling works just fine and I'm able to scroll to the last item in the dataset. Obviously all the content is horizontal now though. After adding horizontal too ScrollView, scrolling works fine horizontally
Any ideas? Could it be some part of my styling I'm not noticing? I'm unable to test this on IOS so I'm not sure if it's an Android specific problem, would be strange though as scrolling worked fine in my other components.
Here's also the Header component code just in case it could be anything in there, although It shouldn't be.
const testImage =
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Restaurant_N%C3%A4sinneula.jpg/800px-Restaurant_N%C3%A4sinneula.jpg";
const testTitle = "Sample Restaurant";
const testDescription = "Thai · Comfort · $$ · 🎫 · 4 ⭐ (217+)";
export default function About() {
return (
<View>
<RestaurantImage image={testImage} />
<RestaurantTitle title={testTitle} />
<RestaurantDescription description={testDescription} />
</View>
);
}
const RestaurantImage = (props) => (
<Image source={{ uri: props.image }} style={{ width: "100%", height: 180 }} />
);
const RestaurantTitle = (props) => (
<Text
style={{
fontSize: 29,
fontWeight: "600",
marginTop: 10,
marginHorizontal: 15,
}}
>
{props.title}
</Text>
);
const RestaurantDescription = (props) => (
<Text
style={{
marginTop: 10,
marginHorizontal: 15,
fontWeight: "400",
fontSize: 15.5,
}}
>
{props.description}
</Text>
);
Wrap your card with TouchableOpacity.
<TouchableOpacity activeOpacity={0} key={index} style={styles.menuItemStyle}>
<FoodDetails food={food} />
<FoodImage food={food} />
</TouchableOpacity>
I hope this thing will work.
I have 2 columns in flatList and I'm trying to align the header component besides the items itself,
Like this
But I got the "add image" above then the items below it,
I'm trying to solve it by using flexWrap in content container style but since I was using numColumns i got a warning that tells flexWrap not supported and use numColumns instead.
so i don't know how can i solve it, so if anybody can help in this case!
here's a snack
Code snippet
const renderItems = ({ item, index }) => {
return (
<View style={{ flex: 0.5, margin: 4 }}>
<View style={[styles.imgContainer, { borderWidth: 0 }]}>
<Image
style={styles.imgStyle}
source={{
uri:
'https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcR1K8ypPsfNVQU8lVxl1i2_ajismMS_w6FA4Q&usqp=CAU',
}}
/>
</View>
</View>
);
};
const renderHeader = () => (
<TouchableOpacity
// onPress={appeandImgs}
style={styles.imgContainer}>
<Image
style={styles.imgStyle}
source={{
uri: 'https://static.thenounproject.com/png/3322766-200.png',
}}
/>
</TouchableOpacity>
);
const keyExtractor = (item, index) => String(index);
<FlatList
data={[1,2,3]}
style={styles.flatList}
numColumns={2}
renderItem={renderItems}
ListHeaderComponentStyle={{
backgroundColor: '#ff0',
width: ScreenWidht / 2 - 20,
}}
keyExtractor={keyExtractor}
ListHeaderComponent={renderHeader}
columnWrapperStyle={{
backgroundColor: '#f07',
}}
contentContainerStyle={{
flexGrow: 1,
paddingBottom: 12,
paddingTop: 15,
}}
/>
I'm trying to map images in an array horizontally, but they're being mapped vertically no matter what I do.
const numberOfRows = Math.ceil(images.length / 3);
const result = Array(numberOfRows)
.fill()
.map((_, rowIndex) => (
<View key={rowIndex}>
{images
.slice(rowIndex * 5, rowIndex * 5 + 5)
.map((image, imageIndex) => (
<TouchableOpacity
key={imageIndex}
onPress={() => alert("image pressed!")}
>
<Image
source={{
uri:
"https://miro.medium.com/max/814/1*Cxm5opOziPF5iavnDSYHLg.png"
}}
style={{ width: 100, height: 100 }}
/>
</TouchableOpacity>
))}
</View>
));
What am I doing wrong here?
The standard flex-direction of a View is vertical. By adding flexDirection: 'row' to your parent View, you can overwrite this behavior.
Code
<View key={rowIndex} style={{flexDirection: 'row'}}>
...
</View>
Working Snack:
https://snack.expo.io/rygY2Vb3H
By default react-native View flex-direction was column. So to align vertically add flexDirection: 'row' to view :
<View key={rowIndex} style={{flex: 1, flexDirection: 'row', flexWrap: 'wrap'}}>
... // your image map code
</View>
You just need to add flexDirection to your View.
<View key={rowIndex} style={{flex: 1, flexDirection: 'row'}}>
...
</View>
You can read more about flexDirection from here.
I'm using SafeAreaView from React Native 0.50.1 and it's working pretty good except for the one part. I assigned the orange background color to the SafrAreaView but can't figure out to change the bottom unsafe area background to black.
Here is the code and I included expected the result and actual result.
What is the best way to make the bottom part of the screen black instead of orange?
import {
...
SafeAreaView
} from 'react-native';
class Main extends React.Component {
render() {
return (
<SafeAreaView style={styles.safeArea}>
<App />
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
...,
safeArea: {
flex: 1,
backgroundColor: '#FF5236'
}
})
I want to have orange top and black bottom.
But below is what I get now.
I was able to solve this using a version of Yoshiki's and Zach Schneider's answers. Notice how you set the top SafeAreaView's flex:0 so it doesn't expand.
render() {
return (
<Fragment>
<SafeAreaView style={{ flex:0, backgroundColor: 'red' }} />
<SafeAreaView style={{ flex:1, backgroundColor: 'gray' }}>
<View style={{ flex: 1, backgroundColor: 'white' }} />
</SafeAreaView>
</Fragment>
);
}
In 2022, I am able to solve this by using edges prop of SafeAreaView!
import { SafeAreaView } from 'react-native-safe-area-context';
<>
<SafeAreaView
edges={["top"]}
style={{ flex: 0, backgroundColor: "#0a2352" }}
/>
<SafeAreaView
edges={["left", "right", "bottom"]}
style={{
flex: 1,
backgroundColor: "#fff",
position: "relative",
}}
>
...
</SafeAreaView>
</>
I was able to solve this by using some absolute position hacking. See the following tweak. Not future proof by any means, but it solves the problem I had.
import {
...
SafeAreaView,
View
} from 'react-native';
class Main extends React.Component {
render() {
return (
<SafeAreaView style={styles.safeArea}>
<App />
<View style={styles.fixBackground} />
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
...,
safeArea: {
flex: 1,
backgroundColor: '#FF5236'
},
fixBackground: {
backgroundColor: 'orange',
position: 'absolute',
bottom: 0,
right: 0,
left: 0,
height: 100,
zIndex: -1000,
}
})
I ran into the same problem and was able to solve with the following:
const styles = StyleSheet.create({
outerWrapper: {
backgroundColor: 'orange',
},
innerWrapper: {
backgroundColor: 'black',
},
});
// snip
const MyComponent = ({ content }) => (
<View style={styles.outerWrapper}>
<SafeAreaView />
<SafeAreaView style={styles.innerWrapper}>
{content}
</SafeAreaView>
</View>
);
The outerWrapper applies the orange background color at the top. The first <SafeAreaView /> pushes the second one down so that it starts at the beginning of the "safe area" (below the status bar). Then the second SafeAreaView takes up the rest of the screen (including the bottom "unsafe" area) and gives it the black background color.
SOLUTION :
I ran into this problem but I don't understand the struggle you guys had to solve it.
The app is in a container that takes all the screen. SafeAreaView will wrap the app into a new smaller container. So add the style into the main container so you can have the background you want. As easy as that :
<View style={{flex:1, backgroundColor: "black"}}>
<SafeAreaView style={{flex:1, backgroundColor: "white"}}>
<App />
</SafeAreaView>
</View>
That's all, hope it helps some of you ;)
You can return multiple SafeAreaViews from your render method using Fragment, each of which independently specify their backgroundColor:
render = () => (
<Fragment>
<SafeAreaView style={{ flex: 0.5, backgroundColor: "red" }} />
<SafeAreaView style={{ flex: 0.5, backgroundColor: "blue" }} />
</Fragment>
);
Result on an iPhone X:
If you need different color of SafeAreaView's top or bottom bar, just cover the bar with View component (positioned absolute) and get its height from useSafeAreaInsets hook.
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
<SafeAreaView style={{ flex: 1 }}>
<View style={{
position: 'absolute'
height: useSafeAreaInsets().top,
backgroundColor:'red'
}} />
</SafeAreaView>
<Fragment> not work correctly on any Android devices, so to solve the issue, i try this solution and it work on both android and ios:
<SafeAreaView style={{ flex:1, backgroundColor: 'red' }}>
<View style={{ flex: 1, backgroundColor: 'green' }} >
<Text>Hello</Text>
</View>
</SafeAreaView>
I just ran into the same problem, we have a navigation bar at the top and a tab bar at the bottom with two different background colors.
My solution was to wrap the tab bar component in a SafeAreaView with the correct background color, along with wrapping the navigation bar component with its own SafeAreaView with its background color.
return (
<SafeAreaView style={{ backgroundColor: 'white' }}>
<Animated.View style={heightAnim}>
{...navBarComponentStuff}
</Animated.View>
</SafeAreaView>)
For the Navigation Bar
And this for the Tab Bar:
<SafeAreaView style={this.props.tabBarStyle}> //Some dark grey color is passed here
<Animated.View style={heightAnim}>
{tabBarItems.map(render tabs.....}
</Animated.View>
</SafeAreaView>}
So, that being said, you could wrap your navigation component in a SafeAreaView with the orange color set in style, and wrap your main content in another SafeAreaView with black as the backgroundColor style, or whatever color you have chosen.
One thing you should know, if you add two SafeAreaView's in the same component like:
return (
<View style={styles.container}>
<SafeAreaView style={{ backgroundColor: 'white' }}>
<NavigationBarComponent />
</SafeAreaView>
<SafeAreaView style={this.props.tabBarStyle}>
<Animated.View style={heightAnim}>
<Animated.View style={[styles.tabBar, this.props.tabBarStyle]}>
{map and render tabs}
</Animated.View>
</Animated.View>
</SafeAreaView>
</View>
);
It will combine the two SafeAreaView's, or at least thats what it looked like to me, maybe somebody with more experience with this can explain what happens in this situation.
This pushed my tab bar to the top of the screen and set the background color to white when I did this.
But moving the top SafeAreaVeiw into the NavigationBarComponent gave me the desired effect.
For those who may be interested, and maybe this is the reason why it acted funky with two of them in the same view, the AnimatedView's are because we sometimes hide the nav and tab bars.
I was able to achieve this by the code below,
<View style={{
flex: 1
}}>
<SafeAreaView style={{
flex: 0,
backgroundColor: 'red'
}}>
</SafeAreaView>
<View style={{
flex: 1,
top: 0,
backgroundColor: 'white'
}}>
</View></View>
Thanks for #Daniel M, inspired by his answer.
I have a FlatList
<View style={styles.container}>
<FlatList data={this.state.restaurants}
renderItem={({ item }) => this.renderItem(item.restaurant)}
keyExtractor={restaurant => restaurant.key}
ListHeaderComponent={() => this.renderHeaderComponent()}
ItemSeparatorComponent={this.renderSeparator}/>
</View>
And have TextInput in header it. I am using TextInput as search bar.
renderHeaderComponent() {
return(
<View style={{ flexDirection: 'row', marginTop: 10, borderBottomColor: '#CED0CE', borderWidth: 1, borderColor: 'transparent' }}>
<Icon name='search' size={30} style={{ marginLeft: 10, marginRight: 10 }}/>
<TextInput
style={{height: 40, flex: 1}}
onChangeText={(text) => this.onChangeText(text)}
placeholder='Type text for search'
clearButtonMode='while-editing'
value={this.state.searchText}
/>
</View>
);
};
In onChangeMethod i filter my data.
onChangeText(text) {
const filteredRestaurants = _.filter(this.props.list, (restaurantObject) => {
const restaurant = restaurantObject.restaurant;
const result = restaurant.name.trim().toLowerCase().includes(text.trim().toLowerCase());
return result;
})
this.setState({
searchText: text,
restaurants: filteredRestaurants
});
}
The problem is following. When I type one symbol in TextInput then focus is lost immediately from TextInput? How can I keep focus in TextInput while typing?
You need to use an auto-bound method for this, as ListHeaderComponent is of type ReactClass, and your current method basically re-creates and re-binds its render every time the data updates, which is not what you want. This concept is further explained in this comment
Anyway, for your example, to fix your issues you should
1) Change your ListHeaderComponent prop to
ListHeaderComponent={this.renderListHeader}
2) Now you want to change your renderHeaderComponent method to be an auto-bound method, and by doing this a new render will not be instantiated every time you change data ( Or enter text into the `TextInput)
renderListHeader = () => (
<View style={{ flexDirection: 'row', marginTop: 10, borderBottomColor: '#CED0CE', borderWidth: 1, borderColor: 'transparent' }}>
<Icon name='search' size={30} style={{ marginLeft: 10, marginRight: 10 }}/>
<TextInput
style={{height: 40, flex: 1}}
onChangeText={(text) => this.onChangeText(text)}
placeholder='Type text for search'
clearButtonMode='while-editing'
value={this.state.searchText}
/>
</View>
)
I ran into this, and to solve it I wrapped the renderListHeader in a React.useMemo hook and passed the state hook as an item to the dependency array.
renderListHeader = useMemo(() => (
<View style={{ flexDirection: 'row', marginTop: 10, borderBottomColor: '#CED0CE', borderWidth: 1, borderColor: 'transparent' }}>
<Icon name='search' size={30} style={{ marginLeft: 10, marginRight: 10 }}/>
<TextInput
style={{height: 40, flex: 1}}
onChangeText={(text) => this.onChangeText(text)}
placeholder='Type text for search'
clearButtonMode='while-editing'
value={this.state.searchText}
/>
</View>
), [this.onChangeText])
This is still an issue for SectionList as of react-native 0.61.5. The auto-bound method doesn't work since the ListHeaderComponent re-renders when data is becomes an empty array.
I used the following work-around :
Move the text input code at the same level than the section list
Using position absolute, position it at the place you want.
Wrap it in an Animated.View
Leverage Animated.event to translate Y the Animated.View
Code sample
const animatedScrollYValue = useRef(new Animated.Value(0)).current;
...
<View>
<Animated.View style={{
position: 'absolute',
top: 142,
left: 30,
right: 30,
zIndex: 1,
transform: [{ translateY: Animated.multiply(animatedScrollYValue, new Animated.Value(-1)) }] }}>
// Your text input
</Animated.View>
<Animated.SectionList
scrollEventThrottle={1}
onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: animatedScrollYValue } } }], { useNativeDriver: true })}
keyExtractor={(item) => item.id}
ListHeaderComponent={// Whatever you want but make you include space for the absolute TextInput}
sections={data}
renderItem={renderItem}
renderSectionHeader={renderHeader}
/>
</View>
I found another workaround for SectionList that seems to work so far, and I'll update this answer if I find it stops working. Rather than rendering my component in ListHeaderComponent I add a dummy section at the start of my data and then use a conditional in renderSectionHeader to render it out.
<SectionList
sections={[{ title: 'header', data: [] }, ...sections]}
renderSectionHeader={({ section }) =>
section.title === 'header' ? (
<MyListHeaderComponent />
) : (
<DefaultSectionHeaderComponent />
)
}
/>
Having worked with some pretty hairy CollectionView screens in Swift/UIKit it's not that different from how we would handle a similar need in that environment so hopefully that means under the hood perf won't be an issue, but again I'll update this answer if that becomes the case.
Another option may be to just add a dummy item to your sections array so that it never becomes empty but I haven't tried that.