Kenny
Kenny

Reputation: 411

React Native horizontal FlatList with multiple rows

I'm trying to implement a horizontal scrolling list that has two rows. Using FlatList, the vertical scrolling list involves setting numColumns but there is no equivalent for using rows with horizontal.

I was successfully able to make it render properly, and it works flawlessly. However, a warning gets thrown saying setting flexWrap is not supported in VirtualizedList or FlatList, and to use numColumns. I cannot use numColumns as that is not meant for horizontal lists.

<FlatList
    horizontal={true}
    contentContainerStyle={{
        flexDirection: 'column',
        flexWrap: 'wrap'
    }}
    {...otherProps}
/>

I found the commit where this warning was added, but cannot find the reasoning behind it. There seems to be no way to make this work without a warning being thrown, at least without ditching FlatList entirely. Is there a more appropriate solution for horizontal lists with rows?

References:

Upvotes: 31

Views: 49496

Answers (10)

Alexander
Alexander

Reputation: 101

if still relevant, you could try something like this.

<FlatList
    inverted={true}
    contentContainerStyle={{
        flexDirection: 'row',
        flexWrap: 'wrap-reverse'
    }}
    {...otherProps}
/>

I don't know why but, when we use flexWrap: 'wrap-reverse', warning is not shown :)

Upvotes: 0

tuanngocptn
tuanngocptn

Reputation: 1471

Please do not use horizontal={true}. For this case you should use numColumns equal to the length of data / 2, and add a <ScrollView> tag. Forcing the number of columns to be half the total will force the list to wrap to the next line.

<ScrollView
  horizontal
  showsHorizontalScrollIndicator={false}
  directionalLockEnabled={true}
  alwaysBounceVertical={false}
>
  <FlatList
    contentContainerStyle={{alignSelf: 'flex-start'}}
    numColumns={Math.ceil(listData.length / 2)}
    showsVerticalScrollIndicator={false}
    showsHorizontalScrollIndicator={false}
    data={listData}
    renderItem={({item, index}) => {
      //push your code
    }}
  />
</ScrollView>

Update 1: edit listData.length / 2 -> Math.ceil(listData.length / 2) following Alex Aung comment. Thanks @alex-aung

Update 2: edit add directionalLockEnabled={true} and alwaysBounceVertical={false} for stoping dragged vertically while dragging. following @amer-nm, Thanks Amer NM

Upvotes: 29

Le Gia Quan
Le Gia Quan

Reputation: 1

Let check it: https://snack.expo.dev/@legiaquan/flatlist-extra-expo?platform=android

FlatListExtra : https://www.npmjs.com/package/flatlist-extra

import { Text, View, StyleSheet } from 'react-native';
import { FlatListExtra } from 'flatlist-extra';

export default function App() {
  const data = [
    { idItem: 1, name: 'one' },
    { idItem: 2, name: 'two' },
    { idItem: 3, name: 'three' },
    { idItem: 4, name: 'four' },
    { idItem: 5, name: 'five' },
    { idItem: 6, name: 'six' },
    { idItem: 7, name: 'seven' },
    { idItem: 8, name: 'eight' },
    { idItem: 9, name: 'nine' },
  ];
  const renderItem = ({ item }) => (
    <View style={{ width: 100, height: 100, borderWidth: 1 }}>
      <Text
        style={{
          alignSelf: 'center',
        }}>
        {item.name}
      </Text>
    </View>
  );
  return (
    <FlatListExtra
      data={data}
      renderItem={renderItem}
      showsHorizontalScrollIndicator={false}
      horizontal
      numRows={2}
      id={'idItem'}
    />
  );
}

Upvotes: 0

James Trickey
James Trickey

Reputation: 1447

I created a wrapper for the FlatList with a numRows prop.

This solution does not use a FlatList inside a ScrollView, as that may nullify some of the FlatList's performance optimisations. Instead it chunks the data and adds column components which can also be styled.

https://www.npmjs.com/package/@idiosync/horizontal-flatlist

Upvotes: 0

Lilly Sharples
Lilly Sharples

Reputation: 1

Easiest way I found (if you wanted x columns, change numColumns to divide data length by x):

return (
    <ScrollView horizontal={true} showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false} >
      <FlatList
        data = {yourData}
        renderItem={renderFunction}
        numColumns={Math.ceil(yourData.length / 2)}
        scrollEnabled={false}
        />
    </ScrollView>
  )

Upvotes: 0

Amer NM
Amer NM

Reputation: 179

tuanngocptn's answer works. However, the two rows horizontal list can also be dragged vertically while dragging it horizontaly which is not that great of a UX.

Adding directionalLockEnabled={true} and alwaysBounceVertical={false} to the ScrollView will solve this issue

<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
directionalLockEnabled={true}
alwaysBounceVertical={false}
>
    <FlatList
        ...
    />
</ScrollView>

Upvotes: 0

Sharaf
Sharaf

Reputation: 1

With some pre-processing work to slice each ROWS_COUNT into a column

const ROWS_COUNT = 5;
const len = Math.ceil(data.length / ROWS_COUNT );
const cols = () => {
    let colsArr = Array(len);
    for (let i = 0; i < len; i++) {
        colsArr[i] = 
            <View style={styles.col}>
                {Array(ROWS_COUNT ).fill(0).map((_, j) => {
                    let index = i * ROWS_COUNT + j;
                    return (index < data.length) ? <NumSquare key={index} num={index} active={index < 23} color={"#44afff"}/> : <></>
                })}
            </View>
                    
    }
    return colsArr;
}
const colsArr = cols();

..................................

<FlatList
    horizontal
    data={colsArr}
    contentContainerStyle={styles.contentContainerStyle}
    showsVerticalScrollIndicator={false}
    showsHorizontalScrollIndicator={false}
    renderItem={({ item, index }) => item}/>

..................................

const styles = StyleSheet.create({
    col:{
        flexDirection:"column",
    }
});

Result:

result

Upvotes: 0

Abdelhalim Ahmed
Abdelhalim Ahmed

Reputation: 61

enhancement the first answer, that's what I did:

const listData = props.data ?? [];
const numColumns = Math.ceil(listData.length / 2);
        <ScrollView
          horizontal
          showsVerticalScrollIndicator={false}
          showsHorizontalScrollIndicator={false}
          contentContainerStyle={{ paddingVertical: 20 }}>
          <FlatList
            scrollEnabled={false}
            contentContainerStyle={{
              alignSelf: 'flex-start',
            }}
            numColumns={numColumns}
            showsVerticalScrollIndicator={false}
            showsHorizontalScrollIndicator={false}
            data={listData}
            renderItem={renderItem}
          />
        </ScrollView>

Upvotes: 6

ronak dholariya
ronak dholariya

Reputation: 195

I use simple logic see

<ScrollView
    horizontal
    showsHorizontalScrollIndicator={false}
    contentContainerStyle={{
        justifyContent: 'center',
        paddingHorizontal: 20,
        paddingBottom: 70,
    }}>
    <View>
        <View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
            {this.state.interest.length
                ? this.state.interest.map((d, i) => {
                    if (i % 2 == 0) {
                        return null;
                    }
                    return (
                        <Intrest
                            style={{
                                height: 40,
                                paddingHorizontal: 20,
                                alignItems: 'center',
                                justifyContent: 'center',
                                borderRadius: 30,
                                margin: 2.5,
                            }}
                            key={i}
                            data={d}
                            add={data => {
                                this._handleAddtoSelection(data);
                            }}
                            remove={data => {
                                this._handleRemoveFromSelection(data);
                            }}
                            active={this._isSelectedTopic(d)}
                        />
                    );
                })
                : null}
        </View>
        <View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
            {this.state.interest.length
                ? this.state.interest.map((d, i) => {
                    if (i % 2 != 0) {
                        return null;
                    }
                    return (
                        <Intrest
                            style={{
                                height: 40,
                                paddingHorizontal: 20,
                                alignItems: 'center',
                                justifyContent: 'center',
                                borderRadius: 30,
                                margin: 2.5,
                            }}
                            key={i}
                            data={d}
                            add={data => {
                                this._handleAddtoSelection(data);
                            }}
                            remove={data => {
                                this._handleRemoveFromSelection(data);
                            }}
                            active={this._isSelectedTopic(d)}
                        />
                    );
                })
                : null}
        </View>
    </View>
</ScrollView>

Output : Output

Upvotes: 3

Shai
Shai

Reputation: 51

I had the same problem with a list of items (a few hundred) that is not that long and i managed to overcome it by going over my list mapping them to multiple views each one with 2 items one after the other (column using flex)

If your list is not too long you can also use ScrollView and it supports flexWrap

Upvotes: 1

Related Questions