Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Render two items (images) side by side in a list view in react native

I'm trying to design something like the below screenshot in react native. Please note each tile is a Product element fetched from the backend.

Product tiles

But I'm unable to do this using a ListView and its renderRow method which is denying me to use any kind of InfiniteScroll Components.

Currently I'm running a loop with 2 elements at a time and rendering 2 elements inside a scroll view. Below is my Code to explain better.

render() {
    var elem = [];
    for(var i = 0; i < this.state.products.length; i+=2) {
      var prod = this.state.products[i];
      var prod2 = this.state.products[i + 1];
      elem.push(
        <View style={styles.view} key={i} >
          <ProductTile onPressAction={this._pdpPage} prod={prod} index={i} />
          <ProductTile onPressAction={this._pdpPage} prod={prod2} index={i + 1} />
        </View>
      );
    }
    return (
        <ScrollView>
          {elem}
        </ScrollView>
    )
}

And then based on the index prop I'm aligning the elements on left or right. My View style looks like below:

view: {
  flex: 1,
  flexDirection: 'row',
},

Please suggest a better way to do this.

Thanks in advance.

like image 600
Karan Gujral Avatar asked May 13 '16 12:05

Karan Gujral


3 Answers

A good way we have done this in production in the past, and it has worked out well, is to get the width of the container and set the width of the cards to 50% of the width, then you can just push all of the single elements into the listview. Also, be sure to set up a flexWrap of wrap.

This will work across all device sizes, and requires not additional modules or libraries.

Check out the sample code below and example here:

https://rnplay.org/apps/t_6-Ag

/* Get width of window */
const width = Dimensions.get('window').width

/* ListView */
<ListView
  contentContainerStyle={styles.listView}
  dataSource={this.state.dataSource}
  renderRow={this.renderRow.bind(this)}
/>

/* Row */
renderRow () {
  return <View style={styles.card}>
           <Text>{rowData.name} {rowData.price}</Text>
         </View>

/* Styles */
listView: {
  flexDirection: 'row',
  flexWrap: 'wrap'
},
card: {
  backgroundColor: 'red',
  width: (width / 2) - 15,
  height: 300,
  marginLeft: 10,
  marginTop: 10
} 
like image 135
Nader Dabit Avatar answered Oct 07 '22 06:10

Nader Dabit


React Native has a good example of this in their CameraRollView.js example. They use a library called groupByEveryN to allow you to set how many items to render per row.

Notice the change to how you initiate the ListView.DataSource...

var ds = new ListView.DataSource({rowHasChanged: this._rowHasChanged});
  this.state.dataSource = ds.cloneWithRows(
    groupByEveryN(this.state.assets, this.props.imagesPerRow)
  );

Your renderRow function just needs to expect an array of items...

// rowData is an array of images
_renderRow: function(rowData: Array<Image>, sectionID: string, rowID: string)  {
  var images = rowData.map((image) => {
    if (image === null) {
      return null;
    }  
    return this.props.renderImage(image);
  });

  return (
    <View style={styles.row}>
      {images}
    </View>
  );
}

Full example file here: https://github.com/facebook/react-native/blob/a564af853f108e071e941ac08be4cde864f5bfae/Examples/UIExplorer/js/CameraRollView.js.

like image 32
BradByte Avatar answered Oct 07 '22 06:10

BradByte


Using https://facebook.github.io/react-native/docs/flexbox as a baseline I've been able to easily do this in my projects by adding flexWrap: 'wrap' as below.

        `<View style={{flex: 1, flexDirection: 'row', flexWrap: 'wrap'}}>
            <View style={{height: 150, width: '50%', backgroundColor: 'powderblue'}} />
            <View style={{height: 150, width: '50%', backgroundColor: 'skyblue'}} />
            <View style={{height: 150, width: '50%', backgroundColor: 'steelblue'}} />
            <View style={{height: 150, width: '50%', backgroundColor: 'powderblue'}} />
            <View style={{height: 150, width: '50%', backgroundColor: 'skyblue'}} />
            <View style={{height: 150, width: '50%', backgroundColor: 'steelblue'}} />
        </View>`

So in your project

         render() {
            var elem = [];
            for(var i = 0; i < this.state.products.length; i+=2) {
            var prod = this.state.products[i];
            var prod2 = this.state.products[i + 1];
            elem.push(
                <View style={styles.view} key={i} >
                <ProductTile onPressAction={this._pdpPage} prod={prod} index={i} />
                <ProductTile onPressAction={this._pdpPage} prod={prod2} index={i + 1} />
                </View>
            );
            }

        return (
            <ScrollView>
                <View style={{flex: 1, flexDirection: 'row', flexWrap: 'wrap'}}>
                    {elem}
                </View>
            </ScrollView>
            )
        }

        view: {
          width: '50%',
          height: 150,
        },

As far as height goes, it needs to be set but may work with 'auto' if you don't have a set height. I didn't test otherwise.

like image 27
ZStoneDPM Avatar answered Oct 07 '22 08:10

ZStoneDPM