Horizontal list issue on SDK23

#1

Hello all,

I have an issue with a horizontal scroll FlatList on SDK 23, and no - it seems to be unrelated to the talked-about regression on the RN Core, as this is both visible on iOS, and does not throw a redbox.

I have a normal/vertical FlatList with a header that displays a horizontal flatlist. The point of the header is to act as a “filter” of the feed in the main list.

On SDK 21, it looks like this:

Which is as expected.

After upgrading to SDK23 following the normal upgrade instructions, it instead looks like this:

Notice the two missing items from the horizontal list. The only shown item is that on index [2]. I’ve verified that the data for the other items are in Redux as expected (using Redux Persist). I’ve also verified that the render() method is called once per item. It also seems by the offset of the shown item that some rendering takes place for the other two items.

As this seems strange to me, I’ve tried reverting to SDK 21 and back again to SDK 23 to be sure, and the issue persists.

Any idea as to what is going wrong?

Thanks :slight_smile:

#2

Did you make sure to set the width of the items?
I noticed in a project that I recently upgraded (16 > 23) that the item width became equal to the contentContainer width… :frowning:
A good way to check this is to use: Toggle Element Inspector :grin:

#3

Thanks for getting back to me :slight_smile: Yes, the width is set to a fixed value as a percentage (15%) of the width of the screen.

Also, seeing as one of the items is shown, I suspect the problem lies elsewhere?

#4

You could try setting getItemLayout. :thinking:

If that doesn’t work then making a snack that reproduces the problem would be super helpful! :upside_down_face:

I’m thinking that "15%" is in relation to the contentContainer and not to the ScrollView… But it’s hard to say without seeing. :sweat_smile:
You could also try setting maxWidth of the item to a pixel value.

If all else fails, DM me and we can ideate :slight_smile:

#5

Thanks again, great suggestions :smiley:

First, a screenshot from element inspector:

I just tried adding

getItemLayout={(data, index) => (
            {length: brandRowWidth, offset: brandRowWidth * index, index}
          )}

but alas, to no avail :slight_smile: Same for setting maxWidth:200 on the row container style.

I’m sorry for the following wall-of-code, but I think all of it is necessary to give a clear picture of what I’m doing. There’s a list which renders another list in it’s header.

The containing list is plain and simple, looks something this:

class CampaignList extends React.Component<ICampaignListProps> {
  ...
  renderHeader = () => {
    return this.props.availableCampaignsData.length > 0 ? (
      <BrandList
        brandData={this.props.availableCampaignsData}
        onPress={this.props.onAvailableCampaignItemPressed}
        chosenCampaignFilter={this.props.chosenCampaignFilter}
      />
    ) : (
      <View />
    );
  };

  render() {
    return (
      <View style={styles.container}>
        <FlatList
          ...
          ListHeaderComponent={this.renderHeader}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

The list in the header, BrandList, is equally straight forward:

class BrandList extends React.Component<IBrandListProps> {
  renderRow = ({ item }) => {
    return (
      <BrandRow
        item={item}
        onPress={this.props.onPress}
        chosenCampaignFilter={this.props.chosenCampaignFilter}
      />
    );
  };

  ...
  render() {
    return (
      <View style={styles.container}>
        <FlatList
          renderItem={this.renderRow}
          horizontal={true}
          contentContainerStyle={{ flex: 1, justifyContent: 'center' }}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    height: dimens.BRAND_LIST_HEIGHT,
    backgroundColor: '#f7fafa',
  },
});

And finally, the row component itself:

class BrandRow extends React.Component<IBrandRowProps> {
  
  render() {
    const { name, logo } = this.props.item.organization;
    return (
      <TouchableOpacity
        onPress={this.onPress}
      >
        <View style={styles.container}>
          <View style={styles.logobackground}>
            <Image source={{ uri: logo }} style={styles.logo} />
          </View>
          <Text
            ellipsizeMode={'tail'}
            numberOfLines={1}
            style={[globalSheet.breadText, styles.logoText]}
          >
            {name}
          </Text>
        </View>
      </TouchableOpacity>
    );
  }
}

const logoSize = dimens.BRAND_LIST_HEIGHT * 0.65;
const brandRowWidth = screenDimens.width * 0.15;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    alignItems: 'center',
    width: brandRowWidth,
    marginHorizontal: 2,
    marginTop: dimens.BRAND_LIST_HEIGHT * 0.1,
  },
  logo: {
    height: logoSize,
    width: logoSize,
    borderRadius: logoSize / 2,
  },
  logobackground: {
    // borderRadius: logoSize / 2,
    // borderWidth: 2,
    // borderColor: 'white',
  },
  logoText: {
    fontSize: 7,
  },
});

Utilities:

import { Dimensions } from 'react-native';
const screenDimens = {
  height: Dimensions.get('window').height,
  width: Dimensions.get('window').width,
};

export { screenDimens };

Common style:

BRAND_LIST_HEIGHT: screenDimens.height * 0.12,
#6

So after posting that monster of a response, I found the error. Some of the occluded code above was in the render method of the row component, which looks like this in full:

render() {
    const { name, logo } = this.props.item.organization;
    const selected =
      this.props.chosenCampaignFilter &&
      this.props.chosenCampaignFilter._id === this.props.item._id;
    return (
      <TouchableOpacity
        onPress={this.onPress}
        activeOpacity={ACTIVE_OPACITY}
        style={this.getCustomBackgroundForSelectedFilter(!!selected)}
      >
        <View style={styles.container}>
          <Image source={{ uri: logo }} style={styles.logo} />
          <Text
            ellipsizeMode={'tail'}
            numberOfLines={1}
            style={[globalSheet.breadText, styles.logoText]}
          >
            {name}
          </Text>
        </View>
      </TouchableOpacity>
    );
  }

and the getCustomBackgroundForSelectedFilter method looks like this:

getCustomBackgroundForSelectedFilter = (selected: boolean) => {
    return selected
      ? {
          backgroundColor: colors.SECONDARY,
          borderRadius: 8,
          flex: 1,
        }
      : { flex: 1 };
  };

I just tried commenting out the style prop for the TouchableOpacity, and it fixed the layout issue. Now I just have to figure out why it causes it.

Thank you very much for rubber-ducking :wink:

#7

For anyone curious, I fixed it by doing conditional styles inline instead of a method call:

render() {
    const { name, logo } = this.props.item.organization;
    const selected =
      this.props.chosenCampaignFilter &&
      this.props.chosenCampaignFilter._id === this.props.item._id;
    return (
      <TouchableOpacity onPress={this.onPress} activeOpacity={ACTIVE_OPACITY}>
        <View
          style={[
            styles.container,
            {
              backgroundColor: selected ? colors.SECONDARY : undefined,
              borderRadius: selected ? 8 : 0,
            },
          ]}
        >
          <Image source={{ uri: logo }} style={styles.logo} />
          <Text
            ellipsizeMode={'tail'}
            numberOfLines={1}
            style={[globalSheet.breadText, styles.logoText]}
          >
            {name}
          </Text>
        </View>
      </TouchableOpacity>
    );
  }
1 Like