react native flat list how to force list items to be the same height?

Viewed 2199

I have a React-Native application where I am using FlatList to display a list of items obtained from the server. The list has 2 columns and I need my list items to be the same height. I put a border around the code rendering my list items but the list items are not the same height. I have tried using flexbox settings to make the view fill the container, but everything I try makes no difference.

I have created a simplified version of my app to illustrate the issue:

enter image description here

See that the red bordered areas are NOT the same height. I need to get these to be the same height.

The grey border is added in the view wrapping the component responsible for a list item and the red border is the root view of the component responsible for a list item. See the code below for clarity.

I can not use the grey border in my application because my application shows empty boxes whilst the component responsible for a list item is getting additional information from the server before it renders itself

enter image description here

Furthermore I can not used fixed sizes for heights.

Application Project structure and code

My code is split up in a manner where the files ending in "container.js" get the data from the server and pass it to its matching rendering component. For example, "MainListContainer" would be getting the list from the server and then pass the list data to "MainList", and "ListItemContainer" would get additional information about the single list item from the server and pass it to "ListItem" to render the actual item. I have kept this model in my simplified application so its as close to my real application as possible.

enter image description here

index.js

import {AppRegistry} from 'react-native';
import MainListContainer from './app/components/MainListContainer';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => MainListContainer);

MainListContainer.js

import React from 'react';
import MainList from './MainList';

const data = [
  {id: '1', title: 'Item 1', subtitle: 'A', description: 'This is the first item.'},
  {id: '2', title: 'Item 2', subtitle: 'B', description: 'The Big Brown Fox Jumped over the lazy dogs. The Big Brown Fox Jumped over the lazy dogs.',},
];

const MainListContainer = () => {
  return ( <MainList items={data} /> );
};

export default MainListContainer;

MainList.js

import React from 'react';
import {StyleSheet, FlatList, View} from 'react-native';
import ListItemContainer from './ListItemContainer';

export default class MainList extends React.Component {
  constructor(props) {
    super(props);
    this.state = { numColumns: 2};
    this.renderItem = this.renderItem.bind(this);
  }

  renderItem({item, index}) {
    return (
      <View style={styles.flatListItemContainer}>  <!-- THIS IS WHERE THE GREY BORDER IS ADDED -->
        <ListItemContainer key={index} item={item} />
      </View>
    );
  }

  render() {
    const {items} = this.props;
    const {numColumns} = this.state;

    return (
      <View>
        <FlatList
          data={items}
          renderItem={this.renderItem}
          numColumns={numColumns}
          key={numColumns}
          keyExtractor={(item) => item.id}
        />
      </View>
    );
  }
};


const styles = StyleSheet.create({

  flatListItemContainer: {
    flex: 1,
    margin: 10,

    borderColor: '#ccc',
    borderWidth: 1,
  },

});

ListItemContainer.js

import React from 'react';
import ListItem from './ListItem';

const ListItemContainer = (props) => {
    const { item } = props;

    return (
        <ListItem item={item} />
    );
};

export default ListItemContainer;

ListItem.js

import React from 'react';
import {TouchableHighlight, View, StyleSheet, Image, Text} from 'react-native';

const ListItem = (props) => {
  const { item } = props;

  return (
    <TouchableHighlight
      underlayColor="white"
    >
      <View style={styles.containerView}> <!-- THIS IS WHERE THE RED BORDER IS ADDED -->

        <View style={styles.top_row}>
          <Image style={styles.image} source={require('../images/placeholder.png')} />
          <View style={styles.title_texts}>
            <Text style={{fontWeight:'bold'}}>{item.title}</Text>
            <Text style={{color: 'rgb(115, 115, 115)'}}>{item.subtitle}</Text>
          </View>
        </View>

        <Text>{item.description}</Text>

      </View>
    </TouchableHighlight>
  );
};

export default ListItem;

const styles = StyleSheet.create({
  containerView: {
    padding: 14,

    borderColor: 'red',
    borderWidth: 1,
  },

  top_row: {
    flex: 1,
    flexDirection: 'row',
    marginBottom: 10,
  },

  title_texts: {
    flex: 1,
    flexDirection: 'column',
  },

  image: {
    alignSelf: 'flex-end',
    resizeMode: 'cover',
    height: 40,
    width: 40,
    marginRight: 20
  },

});

What I have tried

  • ListItem.js : move the style onto the "TouchableHighlight" view
  • ListItem.js : add a view wrapping "TouchableHighlight" view and adding style there
  • ListItem.js : added "alignItems:'stretch' on the "TouchableHighlight, added it to the "containerView" style, tried it on the description field too
  • same as "alignItems" but used "alignedSelf" instead
  • same as "alignItems" but used "alignedContent" instead
  • tried using "flexGrow" on different views (container, description)
3 Answers

You can measure the height of every element in the list and when you determine the maximum height, you can use that height for every element in the list.

const Parent = ({ ...props }) => {
    const [maxHeight, setMaxHeight] = useState<number>(0);
    
    const computeMaxHeight = (h: number) => {
        if (h > maxHeight) setMaxHeight(h);
    }
    return (
        <FlatList
            data={props.data}
            renderItem={({ item }) => (
                <RenderItem 
                    item={item}
                    computeHeight={(h) => computeMaxHeight(h)}
                    height={maxHeight}
                />
            )}
            ....
        />
    )
}

The Items:

const RenderItem = ({...props }) => {
    return (
        <View
            style={{ height: props.height }}
            onLayout={(event) => props.computeHeight(event.nativeEvent.layout.height)}
        >
          <Stuffs />
        </View>
    )
}

This is a very non-performant way of achieving this. I would avoid this if I have a long list or any list of more than a few items. You however can put certain checks in place to limit rerendering etc. Or alternatively if it is only text that will affect the height, then you can only measure the height of the element with the most text and use that element's height for the rest.

Instead of set fixed width height, you can use flex box to achieve it. I just solved the issue by removing alignSelf at the FlatList and add alignItems center on it.

Wrap the flatList in flex box with align item center, you can add the code in your MainList.js file, the first <View>, i.e:

render() {
const {items} = this.props;
const {numColumns} = this.state;

return (
  <View style={{flex: 1, alignItems: 'center'>
    <FlatList
      data={items}
      renderItem={this.renderItem}
      numColumns={numColumns}
      key={numColumns}
      keyExtractor={(item) => item.id}
    />
  </View>
);

If still not reflected, you may try to add flex:1, alignItems center in FlatList style props.

You are missing a very basic concept of giving fixed height to the flatlist items, in your ListItem.js, try to set height:200 in containerView. Let me know if that works for you

Related