Alek Hurst
Alek Hurst

Reputation: 4863

React native flexbox - how to do percentages || columns || responsive || grid etc

After working with react native on iOS for the last couple of weeks, I seem to have come across some shortcomings of flex styling... Particularly when it comes to "responsive" behavior.

For instance, lets say you want to create a view that contains cards (the metadata for these cards comes from an API). You want the cards to be 50% of the view width minus the margin & padding, and to wrap after each 2.

enter image description here

The current implementation I have for this view splits the returned array into rows with 2 items. The list container has flex: 1, flexDirection: 'column, the rows have flex: 1 and then each card has flex: 1. The end result is each row has 2 columns which evenly take up half the view width.

It seems like there is no trivial way to do this in React Native styles, without using javascript to do some sort of pre-processing on the data so that it comes out styled correctly. Does anyone have any suggestions?

Upvotes: 13

Views: 19361

Answers (4)

Sjonchhe
Sjonchhe

Reputation: 828

This might solve this

<View style={{
              flex: 1,
              flexDirection: 'row',
              flexWrap: 'wrap',
            }}>
              {array?.map((item, itemIndex) => {
                return (
                  <View style={{
                    flexBasis: '50%',
                    height:OPTIONAL,
                    width:OPTIONAL
                  }}>
                   // YOUR CONTENT HERE
                  </View>
                )
              })}
            </View>

Upvotes: 1

dazraf
dazraf

Reputation: 81

You can use justifyContent: 'space-between'

<View style={styles.feed}>
    <View style={styles.card} />
    <View style={styles.card} />
    <View style={styles.card} />
    <View style={styles.card} />
 </View>

feed: {
 flex: 1,
 flexDirection: 'row',
 flexWrap: 'wrap',
 padding: 16,
 justifyContent: 'space-between'  }


card: {
 backgroundColor: 'white',
 width: '48%',
 aspectRatio: 1,
 marginBottom: 16   }

screenshot

Upvotes: 2

Ivan Chernykh
Ivan Chernykh

Reputation: 42166

React Native already has percentage support:

<View style={[style.parent]}>
    <View style={[style.child, {backgroundColor: '#996666'} ]} />
    <View style={[style.child, {backgroundColor: '#339966'} ]} />
    <View style={[style.child, {backgroundColor: '#996633'} ]} />
    <View style={[style.child, {backgroundColor: '#669933'} ]} />
</View>

var style = StyleSheet.create({
    parent: {
        width: '100%', 
        flexDirection: 'row', 
        flexWrap: 'wrap'
    },
    child: {
        width: '48%', 
        margin: '1%', 
        aspectRatio: 1,
    }
})

enter image description here

Upvotes: 15

jevakallio
jevakallio

Reputation: 35890

There may be a better way to achieve this with flexbox, but I usually define "percentage" helpers vw and vh for viewport width and viewport height, named after the CSS viewport size units of measurement:

import {Dimensions} from 'react-native';

function vw(percentageWidth) {
  return Dimensions.get('window').width * (percentageWidth / 100);
}

function vh(percentageHeight) {
  return Dimensions.get('window').height * (percentageHeight / 100);
}

To flow items in a grid, you can then calculate an appropriate size for items, accounting for margins and viewport size:

const COLUMNS = 3;
const MARGIN = vw(1);
const SPACING = (COLUMNS + 1) / COLUMNS * MARGIN;

const grid = {
  flex: 1,
  flexWrap: 'wrap',
  flexDirection: 'row',
  justifyContent: 'flex-start'
};

const cell = {
  marginLeft: MARGIN,
  marginTop: MARGIN,
  width: vw(100) / COLUMNS - SPACING
}

return (
  <View style={grid}>
    {this.props.things.map(thing => <View style={cell} />)}
  </View>
)

You should only use this technique if you have a known and limited number of items - for arbitrary amount of cards, you should use ListView for performance reasons, and split your data set to rows manually.

Upvotes: 24

Related Questions