React Native - Horizontal Flatlist with column wrapper doesn't take flex properly as required? - react-native

I have a dynamic flatlist with values that should be rendered in the horizontal list with 3 columns. But, I am not able to make it display as I wanted.
I am trying the code as follow,
let data = ["item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8"];
const { width } = Dimensions.get('window');
const itemWidth = (width - 12) / 3;
<FlatList
columnWrapperStyle={{ justifyContent: 'space-around', alignItems: 'flex-start' }}
numColumns={3}
data={data}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator
keyExtractor={(item, index) => `${index}${item}`}
renderItem={(item) => {
return (
<View style={{ flex: 1, backgroundColor: '#ddd', minWidth: itemWidth, maxWidth: itemWidth }}>
<Text >
Hello World
</Text>
</View>
);
}}
/>
But, the output is not as I wanted.
Let say, if the list is the length of multiple of 3 like 6,9,12 it works as I wanted.
But, if the list has length 8 first two rows are rendered okay. But, the third row is giving full space to the remaining two items. Which I don't want.
Currently i am geeting output as follow
But the output should be like,
Can anyone suggest a workaround?
Thanks.

See i can provide you one logical solution, and i'm assuming you want to plot the text inside the Flatlist. For this kind of scenarios, you would need to append an empty div for the respective empty slots.
If you want the numOfColumns to be 3,
let data = ["item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8"];
First check the remainder and num of rows to be added by
let numOfRowsToBeAdded = 3-(data.length%3);
// here im ommitting if we get 0 as remainder as its not required
if(numOfRowsToBeAdded){
for(let i=0;i<numOfRowsToBeAdded;i++){
data.push(null);
}
}
Hope you got the logic. feel free for doubts. ill help

Since you calculate itemWidth manually, you can use justify: flex-end for your columnWrapperStyle and then add a marginRight: 6 to every first and second item in every row:
renderItem={(item, index) => {
const isThirdColumn = (index + 1) % 3 === 0;
const marginRight = isThirdColumn ? 0 : 6;
return (
<View style={{ marginRight, ...otherStyles }}>

Related

Different numColumns for different parts of Flatlist

I would like to render a React Native Flatlist where the first two elements span the entire width of the screen while all consecutive elements span half the screen. Like this:
Is there a way to do this with one Flatlist? If not, what is the best way to do something like this? Preferably without a Scrollview.
You can't have multiple column numbers in one FlatList but you could achieve what you want by extracting out the first two elements from your data structure and rendering them as a header using the ListHeaderComponent property on FlatList. Then you can render the rest of your array as a regular FlatList.
You need to first redesign your data structure. Then you can do it with View and defined width?
const data = [
{
fields: 'SingleColumn',
},
{
fields: ['First Column','Second Column']
}
]
const renderItem = ({ item }) => {
return <View>
{ somecondition ? <View>
<View style={{ width: '50% }}>
...content...
</View>
<View style={{ width: '50%' }}>
...second column
</View> : <View style={{ width: '100%'}}>
..single column content
</View>
</View>
}
}

React Native: how to combine inline elements so they get wrapped together

I have a list of comma-separated inline touchables like this:
[(<TouchableOpacity><Text>a</Text></TouchableOpacity>),
(<Text>, </Text>),
(<TouchableOpacity><Text>b</Text></TouchableOpacity>),
(<Text>, </Text>),
(<TouchableOpacity><Text>c</Text></TouchableOpacity>)]
rendering as a comma-separated string of items:
a, b, c
The problem is that sometimes comma gets wrapped to a newline, which looks kinda ugly:
a, b, c
, d
How to combine two "inline" elements so they get wrapped together?
Update. Code:
...
let items1 = [];
for (let i = 1; i <= 100; i++) {
const text = makeid(i % 10 + 1);
items1.push((<TouchableOpacity><Text style={{fontSize: 18}}>{text}</Text></TouchableOpacity>));
items1.push((<Text style={{fontSize: 18}}>, </Text>));
}
return (<View style={styles.li}>
{items1}
</View>);
...
// just generates some random string
function makeid(len) {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < len; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
const styles = StyleSheet.create({
li: {
width: "100%",
flexDirection: "row",
flexWrap: "wrap",
marginBottom: 5,
paddingLeft: 10
}
});
Result:
1. Starting solution
According to the official documentation:
Text supports nesting, styling, and touch handling.
So if you're adding TouchableOpacity just to get onPress support, then it's quite useless.
In that case you can simply remove it and wrap all Text blocks in a parent Text and your problem is solved.
As you correctly note, this
leaves a user without nice animation provided by <TouchableOpacity>.
I thought to a better solution and it was not so far :)
2. Other (and better?) solution
Simply wrap your <TouchableOpacity> and related <Text> with comma in a single <View> with flexDirection: 'row' and add it to the array you're going to render.
render() {
let items1 = [];
for (let i = 1; i <= 100; i++) {
const text = makeid((i % 10) + 1);
items1.push(
<View style={{ flexDirection: 'row'}}>
<TouchableOpacity>
<Text style={{ fontSize: 18 }}>{text}</Text>
</TouchableOpacity>
<Text style={{ fontSize: 18 }}>, </Text>
</View>
);
}
return (
<View style={styles.li}>{items1}</View>
);
}
This way you're sure comma do not separate from its previous word and you benefit of <TouchableOpacity> animation.
Here you can find a working example.
Hope this helps!

Is there a way for keeping the title aligned, when adding a subtitle, in React Native Elements?

I'm using React Native Elements (1.0) to display my <ListItem />s. I was wondering if there is a way for keeping the title on the same line it was when adding a subtitle?
Here is what I currently have, which is wrong:
What I would like to have is the "1x" to be aligned with "Filterkaffee" and "Klein" and have the subtitle below.
Edit
Also here is the ListItem's code:
<ListItem
title={item.name}
leftElement=<Text style={styles.amount}>{item.amount}x</Text>
rightTitle={`${item.price.label}`}
subtitle={`${
item.items.length > 0
? item.customChoiceItems.reduce((acc, customChoiceItem, index, arr) => {
acc += customChoiceItem.name;
acc += index < arr.length - 1 ? "\n" : "";
return acc;
}, "")
: null
}`}
onPress={() => {}}
/>
Since you're using the leftElement prop on the ListItem component; I'm assuming you're using version 1.0.0-beta5 of react-native-elements.
The ListItem component has a containerStyle prop that you can use to control the styling of the container. By default, it has an alignItems: 'center' rule that allows the contents of the ListItem to be centred vertically. Since you want to align them to the top, you can use alignItems: 'flex-start'.
<ListItem
title="Filterkaffee"
leftElement={<Text style={styles.amount}>1x</Text>}
subtitle="Medium Roast Schokosojakeks"
rightTitle="Klein"
containerStyle={{ alignItems: 'flex-start' }}
/>

React Native: Correct scrolling in horizontal FlatList with Item Separator

ReactNative: v0.52.0
Platform: iOS
My FlatList code:
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
legacyImplementation={false}
data={this.props.photos}
renderItem={item => this.renderPhoto(item)}
keyExtractor={photo => photo.id}
ItemSeparatorComponent={this.itemSeparatorComponent}
/>
Item separator code:
itemSeparatorComponent = () => {
return <View style = {
{
height: '100%',
width: 5,
backgroundColor: 'red',
}
}
/>
}
And finally FlatList item component:
renderPhoto = ({ item, index }) => {
return (
<View style = {{ width: SCREEN_WIDTH, height: 'auto' }}>
<FastImage
style = { styles.photo }
resizeMode = { FastImage.resizeMode.contain }
source = {{ uri: item.source.uri }}
/>
</View>
)
}
But when scrolling, the FlatList makes an offset to the separator but not to the left edge of item:
And with each new element the FlatList adds the width of the all previous separators to offset:
How to make the FlatList component consider the width of the separator component in horizontal scrolling and make proper offset?
I had the same use-case. For anyone looking for a solution, here it is.
Step 1) Don't use ItemSeparatorComponent prop. Instead, render it inline in your renderItem component.
Step 2) (Key-point). Specify the width and height in the style prop of the FlatList. The width, in your case, should be SCREEN_WIDTH + 5.
Then Flatlist will automatically move the entire screen (photo + separator) away when pagination is enabled. So now your code should be like so:-
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
legacyImplementation={false}
data={this.props.photos}
renderItem={item => this.renderPhoto(item)}
keyExtractor={photo => photo.id}
style={{width: SCREEN_WIDTH + 5, height:'100%'}}
/>
Render photo code:-
renderPhoto = ({ item, index }) => {
return (
<View style = {{ width: SCREEN_WIDTH + 5, height: 'auto',
flexDirection:'row'}}>
<FastImage
style = { styles.photo }
resizeMode = { FastImage.resizeMode.contain }
source = {{ uri: item.source.uri }}
/>
{this. itemSeparatorComponent()}
</View>
)}
Item separator code:
itemSeparatorComponent = () => {
return <View style = {
{
height: '100%',
width: 5,
backgroundColor: 'red',
}
}
/>
}
If you still can't figure it out, then look at this component:
https://github.com/zachgibson/react-native-parallax-swiper
Try to go into the implementation, you will see that this guy has provided width and height to the Animated.ScrollView.
https://github.com/zachgibson/react-native-parallax-swiper/blob/master/src/ParallaxSwiper.js
Line number: 93 - 97
The top-level view you're returning in the renderPhoto function has a width of SCREEN_WIDTH, yet the ItemSeparatorComponent, which renders in between each item, is taking up a width of 5 as per your style definition. Consequently, for each additional item you scroll to, that initial offset will become 5 more pixels on the left.
To fix this, you can either remove the ItemSeparatorComponent completely, (as you already have pagingEnabled set to true), or set the width of the top-level view returned in renderPhoto equal to SCREEN_WIDTH - 2.5. That way you'll see half of the item separator on the right edge of one photo and the other half on the left edge of the next photo.
Actually, one other possible solution could be to remove the item separator, set the renderPhoto View's width to SCREEN_WIDTH + 5, and then include these additional properties inside the style: {paddingRight: 5, borderRightWidth: 5, borderRightColor: 'red'}. That way the red separator won't be visible until scrolling left and right, because of the pagingEnabled property.

React Native FlatList with columns, Last item width

I'm using a FlatList to show a list of items in two columns
<FlatList style={{margin:5}}
data={this.state.items}
numColumns={2}
keyExtractor={(item, index) => item.id }
renderItem={(item) => <Card image={item.item.gallery_image_url} text={item.item.name}/> }
/>
The card component is just a view with some styles:
<View style={{ flex: 1, margin: 5, backgroundColor: '#ddd', height: 130}} ></View>
It is working fine, but if the number of items is odd, the last row only contains one item and that item stretches to the full width of the screen.
How can I set the item to the same width of the others?
for your case use flex: 1/2
therefore: Your item should have flex of 1/(number of columns) if you have 3 columns your item should have flex:1/3
Theres a few things you can try here.
A) Setting a pre-defined width for the card ( Maybe equal to the height you've set? ). Then you can use alignItems in order to have the card positioned in the middle or on the left - Unsure as to which you wanted here.
B) If there are an even number of cards, you could add an empty View at the end in order to fill this space. I find this method pretty clunky but useful when trying to leave space for future elements.
C) Simply use alignItems: 'space-between, i like to use this to center items, but you would have to define the width, or use something like flex:0.5
I suggest researching more into flexbox to help you with this, as it is hard to tell the context of this situation. I'm assuming the above methods will help, but if not, here are some links for you to look at -
A Complete Guide to Flexbox (CSS Tricks)
Layout with Flexbox (React Native)
Video Tutorial (Youtube) Link Broken
Hope this helps. If you need any further clarification - just ask
This is the cleanest way to style a FlatList with columns and spaced evenly:
<FlatList style={{margin:5}}
numColumns={2} // set number of columns
columnWrapperStyle={style.row} // space them out evenly
data={this.state.items}
keyExtractor={(item, index) => item.id }
renderItem={(item) => <Card image={item.item.gallery_image_url} text={item.item.name}/> }
/>
const style = StyleSheet.create({
row: {
flex: 1,
justifyContent: "space-around"
}
});
You can try to get the current width of the device via Dimensions, do some math based on the number of columns you want to render, minus off the margins and set that as the minWidth and maxWidth.
For example:
const {height, width} = Dimensions.get('window');
const itemWidth = (width - 15) / 2;
<View style={{ flex: 1, margin: 5, backgroundColor: '#ddd', minWidth: {this.itemWidth}, maxWidth: {this.itemWidth}, height: 130}} ></View>
The reason for it is your Card have style flex: 1, so it will try to expand all the space remain.
You can fix it by add maxWidth: '50%' to your Card style
<View style={{ flex: 1, margin: 5, backgroundColor: '#ddd', height: 130, maxWidth: '50%'}} ></View>
#Emilius Mfuruki suggestion is good, but if you have text with varying length it doesn't work perfectly. Then use this width inside your item view:
const {height, width} = Dimensions.get('window');
const itemWidth = (width - (MarginFromTheSide * 2 + MarginInBetween * (n-1))) / n;
In FlatList use:
columnWrapperStyle={{
flex: 1,
justifyContent: 'space-evenly',
}}
Works perfectly.
The simplest solution is do the math.
Imagine we have 2 View for each Row and we want to give 10 margin to every side it will look something like that:
As you see in the image above each View have 2 margins in horizontal. (inside of red rectangle)
So we have to subtract the product of margin, number of column and 2 from the width.
import { Dimensions } from 'react-native';
const {width} = Dimensions.get("window")
const column = 2
const margin = 10
const SIZE = (width - (margin * column * 2)) / column
<View style={{ margin: 10, width: SIZE }} ></View>
I tried some of the solutions above but I still had some problems with the margins on the last item (2 columns list).
My solution was simply wrapping the item into a parent container, leaving the original container with flex: 1 and the parent container of the item with flex: 0.5 so it would take the margin correctly.
itemContainer: {
flex: 0.5,
},
itemSubContainer: {
flex: 1,
marginHorizontal: margin,
},
A simple way with flex
<FlatList style={{margin:5}}
data={this.state.items}
numColumns={2}
keyExtractor={(item, index) => item.id }
renderItem={({item, index}) => {
const lastItem = index === this.state.items.length - 1;
return (
<View style={{flex: lastItem ? 1 / 2 : 1 }}>
<Card image={item.gallery_image_url} text={item.name}/>
</View>
)}}
/>
You can use ListFooterComponent={this.renderFooter}
None of the above answers have worked perfectly for me so I post my own answer:
works with padding and margins
the last element will always have the correct size
<FlatList
data={data}
numColumns={2}
renderItem={({item, index}) => {
const lastItem = index === data.length - 1;
return (
<View style={{flex: 1, padding: 8, maxWidth: lastItem ? '50%' : '100%' }}>
...
</View>
)}}
/>
Note: change maxWidth according to number of columns
Result:
just use flex:0.5 and width:'50%'
Create an array with odd number of images in it, like:
const images = [
require('./../...jpg'),
require('./../...jpg'),
require('./../...jpg'),
require('./../...jpg'),
require('./../...jpg'),
];
And then, use the code given below,
const App = () => {
const _renderItem = ({ item, index }) => (
<Image
source={item}
style={{
width: '50%',
height: 200,
}}
resizeMode="cover"
/>
);
return (
<View style={{flex: 1, marginHorizontal: 10,}}>
<FlatList
columnWrapperStyle={{ justifyContent: 'space-between' }}
keyExtractor={(_, index)=> index.toString()}
data={images}
numColumns={2}
renderItem={_renderItem}
/>
</View>
)
};
export default App;
Working Example