2 column layout with flexBox on React Native - react-native

I'm trying to do a two column layout in React Native from a list of items. It only seems to work if I define the width of the items, I would like to define just a percentage of the parent width (0.5 would make a 2 column layout, but 0.25 would make a 4 column one). Can this be done?
export default class App extends Component {
render() {
return (
<View style={[styles.container, {width:width}]}>
<View style={styles.item}><Text>{'item1'}</Text></View>
<View style={styles.item}><Text>{'item2'}</Text></View>
<View style={styles.item}><Text>{'item3'}</Text></View>
<View style={styles.item}><Text>{'item4'}</Text></View>
<View style={styles.item}><Text>{'item4'}</Text></View>
<View style={styles.item}><Text>{'item5'}</Text></View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
},
item :{
flex: 0.5, //why this doesnt work???
// width: 150, //using fixed item width instead of flex: 0.5 works
height: 100,
padding: 10,
backgroundColor: 'red',
// flexGrow: 1,
// flexShrink: 0,
}
});
You can play with it here: https://snack.expo.io/SyBjQuRxm
Css working equivalent: https://codepen.io/klamping/pen/WvvgBX?editors=110
Obviously I could do something like creating a container for each column, but that's not the point:
render() {
return (
<View style={[styles.container, {width:width}]}>
<View style={styles.column1}>
<View style={styles.item}><Text>{'item1'}</Text></View>
<View style={styles.item}><Text>{'item2'}</Text></View>
<View style={styles.item}><Text>{'item3'}</Text></View>
</View>
<View style={styles.column2}>
<View style={styles.item}><Text>{'item4'}</Text></View>
<View style={styles.item}><Text>{'item4'}</Text></View>
<View style={styles.item}><Text>{'item5'}</Text></View>
</View>
</View>
);
}

It is possible if you use percentage values for the widths:
<View style={styles.container}>
<View style={styles.item}>
...
</View>
</View>
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'flex-start' // if you want to fill rows left to right
},
item: {
width: '50%' // is 50% of container width
}
})

you can try scrollview with flatlist. the code below generates 2 columns. if you want 3 columns change numcolumn={data.length/3} etc.
<ScrollView
horizontal
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
flexDirection: 'row',
flexWrap: 'wrap',
}}>
<FlatList
data={data}
renderItem={this.renderItem}
keyExtractor={item => `${item.id}`}
showsHorizontalScrollIndicator={false}
numColumns={data.length / 2}
/>
</ScrollView>

You could map the list of element into a an array of pairs.
Then render the pairs as a row inside a list
<View className="px-4 ">
{Buttons.map((row, i) => {
return (
<View className="flex-1 flex-row" key={i}>
{row.map((item, j) => {
return <ThumbButton key={j} {...item} />;
})}
</View>
);
})}
</View>
For making the nested array you could use this function https://stackoverflow.com/a/44937519/1807666
function combineTwo(inputArray: Array<object>) {
var result = [];
for (var i = 0; i < inputArray.length; i += 2) {
result.push([inputArray[i], inputArray[i + 1]]);
}
return result;
}'''

Related

Set the number of rows in FlatList dynamically

I am trying to create an horizontal list of element where the width is fix but the height is dynamic. That means if no more avatars could be fit in a row, a new row should be created.
const DATA = ["cQpBBiCQ3BwCHKXnTJeKt4yQ2", "zHYYvrVPY1edwOrQhQLt6k1", "zHYYvrVPYedwOrQhQLt6k1", "zHYYvrVPY1edwOrQhQLt6k1", "zHYYvrVPedwOrQhQLt6k1", "zHYYvrVPrQhQLt6k1", "zHYYvsdPrQhQLt6k1"];
const renderConfirmedUserAvatar = ({ item }) => {
if (users[item]) {
return (
<View
style={styles.avatarView}>
<Avatar
size={40}
source={{ width: 80, height: 80, uri: users[item].avatar }}
/>
<Text category={'c2'}>{users[item].userName}</Text>
</View>
)
}
};
<View style={styles.confirmedContainer}>
<FlatList
data={DATA}
renderItem={renderConfirmedUserAvatar}
keyExtractor={item => item}
contentContainerStyle={{ alignSelf: 'flex-start' }}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
horizontal={true}
/>
</View>
const styles = StyleSheet.create({
avatarView: {
alignSelf: 'flex-start',
alignItems: 'center',
margin: 7,
},
confirmedContainer:{
width: 200,
flex: 1,
}
});
With the current code the avatars are shown in one row and only three and a half of them are shown, the other are hidden because of the width.
In this case the expected result is to show the avatars in 3 rows, three avatars in the first two rows and one avatar in the last one.
Try adding flexWrap: 'wrap' to the styles of the view which wraps flatlist

KeyboardAwareScrollView has extra bottom padding?

I've had this problem for a while with KeyboardAwareScrollView, where it has some extra padding at the bottom (yellow). Here I want my form to be always at the bottom of the screen, this it seems this padding does not permit this.
export const LoginView: React.FC = () => {
return (
<View style={styles.container}>
<KeyboardAwareScrollView
style={styles.scrollContainer}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
>
<View
style={{
justifyContent: "space-between",
backgroundColor: "green",
}}
>
<View style={styles.imageContainer}>
<Image></Image>
</View>
<View style={styles.formConstainer}>
<Formik></Formik>
</View>
</View>
</KeyboardAwareScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
scrollContainer: {
backgroundColor: "yellow",
},
imageContainer: {
alignItems: "center",
justifyContent: "center",
},
formConstainer: {},
});
Here's how it looks like right now.
Just change KeyboardAwareScrollView style to contentContainerStyle ( These styles will be applied to the scroll view content container which wraps all of the child views. )
and add flex to view inside it.
export const LoginView: React.FC = () => {
return (
<View style={styles.container}>
<KeyboardAwareScrollView
contentContainerStyle={styles.scrollContainer} //style changed to contentContainerStyle
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
>
<View
style={{
justifyContent: "space-between",
backgroundColor: "green",
flex:1 //flex added
}}
>
<View style={styles.imageContainer}>
<Image></Image>
</View>
<View style={styles.formConstainer}>
<Formik></Formik>
</View>
</View>
</KeyboardAwareScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
scrollContainer: {
backgroundColor: "yellow",
flexGrow:1 //added flexGrow
},
imageContainer: {
alignItems: "center",
justifyContent: "center",
flex:2 //flex added
},
formConstainer: {
flex:1 //flex added
},
});
Make the contentInset to be dynamic based on the keyboard hide and view.
set the contentBottom value based on your design.
Values in the solution are based on my design
const [contentBottom, setContentBottom] = useState(0);
<KeyboardAwareScrollView
style={{
flex: 1,
backgroundColor: Color.white,
}}
keyboardOpeningTime={0}
extraScrollHeight={150}
enableResetScrollToCoords
onKeyboardWillHide={() => setContentBottom(0)}
onKeyboardWillShow={() => setContentBottom(200)}
contentInset={{ bottom: contentBottom }}
>

Flex property not being applied on TouchableOpacity

I'm following this tutorial and got stuck with the dynamic TouchableOpacity elements. For some reason, the flex property is not being applied. When I view the elements using chrome's dev tools I can see them but they don't have a flex property. If I add it manually, I get the look I want.
Here is the render snippet:
render() {
const { rgb, size } = this.state;
const { width } = Dimensions.get('window');
return (
<View style={styles.container}>
<Header fontSize={40} />
<View
style={{
height: width * 0.875,
width: width * 0.8758,
flexDirection: 'row',
}}
>
{Array(size)
.fill()
.map((val, columnIndex) => (
<View
style={{ flex: 2, flexDirection: 'column' }}
key={columnIndex}
>
{Array(size)
.fill()
.map((val, rowIndex) => (
<TouchableOpacity
key={`${rowIndex}.${columnIndex}`}
style={{
flex: 1,
backgroundColor: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
margin: 2,
}}
onPress={() =>
console.log(
rowIndex,
columnIndex
)
}
/>
))}
</View>
))}
</View>
</View>
);
}
Stupid mistake. Was importing TouchableOpacity from the wrong package. Check that you import from react-native

Last odd flatlist item stretches along screen

I'm displaying some data items with Flatlist in two columns. But when there is an odd item at the last place, it stretches all along the screen. I don't want that, how can I make it to take only %50 of screen space like even items? Thanks a lot.
CategoryGridTile Component:
return (
<View style={styles.PlaceItem}>
<TouchableCmp onPress={props.onSelect}>
<View>
<View style={{ ...styles.placeRow, ...styles.placeHeader }}>
<ImageBackground
source={{ uri: props.image }}
style={styles.bgImage}
>
<View style={styles.titleContainer}>
<Text style={styles.title} numberOfLines={2}>
{props.title}
</Text>
</View>
</ImageBackground>
</View>
</View>
</TouchableCmp>
</View>
)
}
const styles = StyleSheet.create({
PlaceItem: {
flex: 1,
height: 125,
width: '80%',
backgroundColor: '#f5f5f5',
borderRadius: 5,
overflow: 'hidden',
margin: 6
},
placeRow: {
flexDirection: 'row'
}
})
Screen:
const renderGridItem = itemData => {
return (
<CategoryGridTile
image={itemData.item.image}
title={itemData.item.title}
onSelect={() => {
props.navigation.navigate({
routeName: 'CategoryPlaces',
params: {
categoryId: itemData.item.title,
locationId: locations
}
})
}}
/>
)
}
return (
<FlatList
keyExtractor={(item, index) => item._id}
data={displayedCategories}
renderItem={renderGridItem}
numColumns={2}
/>
)
Changing PlaceItem flex: 1 to flex: 1/2 at CategoryGridTile component solved the problem.

How to make a view 100% window height?

I'm brand new to react native. I'm trying to figure out how to make a box full height.
I have tried to add flex property to both container and view, but the height is not 100%.
export default class LinksScreen extends React.Component {
render() {
return (
<ScrollView style={styles.container}>
<View style={{ flex: 1, backgroundColor: '#fff', flexGrow: 1 }}>
<Text>Hallo</Text>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
padding: 20,
backgroundColor: 'blue',
},
});
You need to wrap the ScrollView with a View that has height. ScrollViews must have a bounded height in order to work. You can read more details about that here: https://facebook.github.io/react-native/docs/scrollview
Do it something like below:
render() {
return (
<View style={styles.container}>
<View style={{height: 80}} >
<ScrollView
...
EDIT
Your code actually works. Maybe you just want to see if your scrollview works when the whole screen is occupied. Try this below and you will see, I simply multiplied the text contents 80 times:
render() {
return (
<ScrollView style={styles.container}>
<View style={{ flex: 1, backgroundColor: "#fff", flexGrow: 1 }}>
{Array(80)
.fill(1)
.map((item, index) => (
<Text>Hallo {index}</Text>
))}
</View>
</ScrollView>
);
}
Remove flex:1
Give height:"100%"
width: "100%"